From 5db7f03ea6a412374925fa9534aa9ed39ad9951d Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 13 May 2026 14:03:21 +0100 Subject: [PATCH 1/4] Scaffolding for the SubnetPool controller $ go run ./cmd/scaffold-controller -interactive=false \ -gophercloud-client=NewNetworkV2 \ -kind SubnetPool \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools \ -import-dependency Project \ -import-dependency AddressScope \ -optional-create-dependency Project \ -optional-create-dependency AddressScope --- api/v1alpha1/subnetpool_types.go | 102 +++++++ api/v1alpha1/zz_generated.deepcopy.go | 85 ++++++ cmd/models-schema/zz_generated.openapi.go | 126 ++++++++ config/rbac/role.yaml | 2 + .../openstack_v1alpha1_subnetpool.yaml | 14 + internal/controllers/subnetpool/actuator.go | 288 ++++++++++++++++++ .../controllers/subnetpool/actuator_test.go | 119 ++++++++ internal/controllers/subnetpool/controller.go | 156 ++++++++++ internal/controllers/subnetpool/status.go | 65 ++++ .../subnetpool-create-full/00-assert.yaml | 38 +++ .../00-create-resource.yaml | 43 +++ .../subnetpool-create-full/00-secret.yaml | 6 + .../tests/subnetpool-create-full/README.md | 11 + .../subnetpool-create-minimal/00-assert.yaml | 27 ++ .../00-create-resource.yaml | 14 + .../subnetpool-create-minimal/00-secret.yaml | 6 + .../subnetpool-create-minimal/01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../tests/subnetpool-create-minimal/README.md | 15 + .../subnetpool-dependency/00-assert.yaml | 45 +++ .../00-create-resources-missing-deps.yaml | 40 +++ .../subnetpool-dependency/00-secret.yaml | 6 + .../subnetpool-dependency/01-assert.yaml | 45 +++ .../01-create-dependencies.yaml | 32 ++ .../subnetpool-dependency/02-assert.yaml | 23 ++ .../02-delete-dependencies.yaml | 11 + .../subnetpool-dependency/03-assert.yaml | 11 + .../03-delete-resources.yaml | 13 + .../tests/subnetpool-dependency/README.md | 21 ++ .../00-assert.yaml | 19 ++ .../00-import-resource.yaml | 40 +++ .../00-secret.yaml | 6 + .../01-assert.yaml | 34 +++ .../01-create-trap-resource.yaml | 42 +++ .../02-assert.yaml | 39 +++ .../02-create-resource.yaml | 41 +++ .../03-assert.yaml | 8 + .../03-delete-import-dependencies.yaml | 9 + .../04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../subnetpool-import-dependency/README.md | 29 ++ .../subnetpool-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 28 ++ .../subnetpool-import-error/00-secret.yaml | 6 + .../subnetpool-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/subnetpool-import-error/README.md | 13 + .../tests/subnetpool-import/00-assert.yaml | 15 + .../subnetpool-import/00-import-resource.yaml | 15 + .../tests/subnetpool-import/00-secret.yaml | 6 + .../tests/subnetpool-import/01-assert.yaml | 34 +++ .../01-create-trap-resource.yaml | 17 ++ .../tests/subnetpool-import/02-assert.yaml | 33 ++ .../subnetpool-import/02-create-resource.yaml | 14 + .../tests/subnetpool-import/README.md | 18 ++ .../tests/subnetpool-update/00-assert.yaml | 26 ++ .../00-minimal-resource.yaml | 14 + .../tests/subnetpool-update/00-secret.yaml | 6 + .../tests/subnetpool-update/01-assert.yaml | 17 ++ .../01-updated-resource.yaml | 10 + .../tests/subnetpool-update/02-assert.yaml | 26 ++ .../02-reverted-resource.yaml | 7 + .../tests/subnetpool-update/README.md | 17 ++ internal/osclients/subnetpool.go | 104 +++++++ test/apivalidations/subnetpool_test.go | 132 ++++++++ website/docs/crd-reference.md | 10 + 66 files changed, 2288 insertions(+) create mode 100644 api/v1alpha1/subnetpool_types.go create mode 100644 config/samples/openstack_v1alpha1_subnetpool.yaml create mode 100644 internal/controllers/subnetpool/actuator.go create mode 100644 internal/controllers/subnetpool/actuator_test.go create mode 100644 internal/controllers/subnetpool/controller.go create mode 100644 internal/controllers/subnetpool/status.go create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-full/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-full/README.md create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-minimal/01-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-create-minimal/README.md create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/01-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/02-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/03-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-dependency/README.md create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-dependency/README.md create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-error/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-error/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-error/01-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-error/01-import-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import-error/README.md create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-import/README.md create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/00-secret.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/README.md create mode 100644 internal/osclients/subnetpool.go create mode 100644 test/apivalidations/subnetpool_test.go diff --git a/api/v1alpha1/subnetpool_types.go b/api/v1alpha1/subnetpool_types.go new file mode 100644 index 000000000..f7f9a93e8 --- /dev/null +++ b/api/v1alpha1/subnetpool_types.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. +*/ + +package v1alpha1 + +// SubnetPoolResourceSpec contains the desired state of the resource. +type SubnetPoolResourceSpec 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"` + + // 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"` + + // addressScopeRef is a reference to the ORC AddressScope which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="addressScopeRef is immutable" + AddressScopeRef *KubernetesNameRef `json:"addressScopeRef,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/networking/v2/extensions/subnetpools + // + // 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"` +} + +// SubnetPoolFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type SubnetPoolFilter struct { + // name of the existing resource + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description of the existing resource + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // addressScopeRef is a reference to the ORC AddressScope which this resource is associated with. + // +optional + AddressScopeRef *KubernetesNameRef `json:"addressScopeRef,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/networking/v2/extensions/subnetpools +} + +// SubnetPoolResourceStatus represents the observed state of the resource. +type SubnetPoolResourceStatus 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"` + + // projectID is the ID of the Project to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // addressScopeID is the ID of the AddressScope to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + AddressScopeID string `json:"addressScopeID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the SubnetPool structure from + // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4e7d7e3c1..8abd3f2fb 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6455,6 +6455,91 @@ func (in *SubnetList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolFilter) DeepCopyInto(out *SubnetPoolFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.AddressScopeRef != nil { + in, out := &in.AddressScopeRef, &out.AddressScopeRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolFilter. +func (in *SubnetPoolFilter) DeepCopy() *SubnetPoolFilter { + if in == nil { + return nil + } + out := new(SubnetPoolFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolResourceSpec) DeepCopyInto(out *SubnetPoolResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.AddressScopeRef != nil { + in, out := &in.AddressScopeRef, &out.AddressScopeRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolResourceSpec. +func (in *SubnetPoolResourceSpec) DeepCopy() *SubnetPoolResourceSpec { + if in == nil { + return nil + } + out := new(SubnetPoolResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolResourceStatus) DeepCopyInto(out *SubnetPoolResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolResourceStatus. +func (in *SubnetPoolResourceStatus) DeepCopy() *SubnetPoolResourceStatus { + if in == nil { + return nil + } + out := new(SubnetPoolResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubnetResourceSpec) DeepCopyInto(out *SubnetResourceSpec) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 29d2fa1b1..cb2417821 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -246,6 +246,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetImport(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), @@ -12011,6 +12014,129 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref common. } } +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolFilter defines an existing resource by its properties", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "addressScopeRef": { + SchemaProps: spec.SchemaProps{ + Description: "addressScopeRef is a reference to the ORC AddressScope which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolResourceSpec 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: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "addressScopeRef": { + SchemaProps: spec.SchemaProps{ + Description: "addressScopeRef is a reference to the ORC AddressScope which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolResourceStatus 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: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the ID of the Project to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "addressScopeID": { + SchemaProps: spec.SchemaProps{ + Description: "addressScopeID is the ID of the AddressScope to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a04a488e2..8e416daee 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -38,6 +38,7 @@ rules: - servers - services - sharenetworks + - subnetpools - subnets - trunks - users @@ -75,6 +76,7 @@ rules: - servers/status - services/status - sharenetworks/status + - subnetpools/status - subnets/status - trunks/status - users/status diff --git a/config/samples/openstack_v1alpha1_subnetpool.yaml b/config/samples/openstack_v1alpha1_subnetpool.yaml new file mode 100644 index 000000000..6ea0df075 --- /dev/null +++ b/config/samples/openstack_v1alpha1_subnetpool.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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 SubnetPool + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/subnetpool/actuator.go b/internal/controllers/subnetpool/actuator.go new file mode 100644 index 000000000..13c530e50 --- /dev/null +++ b/internal/controllers/subnetpool/actuator.go @@ -0,0 +1,288 @@ +/* +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 subnetpool + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools" + 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 = subnetpools.SubnetPool + + 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 subnetpoolActuator struct { + osClient osclients.SubnetPoolClient + k8sClient client.Client +} + +var _ createResourceActuator = subnetpoolActuator{} +var _ deleteResourceActuator = subnetpoolActuator{} + +func (subnetpoolActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator subnetpoolActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetSubnetPool(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator subnetpoolActuator) 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 := subnetpools.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListSubnetPools(ctx, listOpts), true +} + +func (actuator subnetpoolActuator) 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 + + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + addressScope, rs := dependency.FetchDependency[*orcv1alpha1.AddressScope]( + ctx, actuator.k8sClient, obj.Namespace, + filter.AddressScopeRef, "AddressScope", + orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := subnetpools.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + ProjectID: ptr.Deref(project.Status.ID, ""), + AddressScopeID: ptr.Deref(addressScope.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListSubnetPools(ctx, listOpts), reconcileStatus +} + +func (actuator subnetpoolActuator) 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 projectID string + if resource.ProjectRef != nil { + project, projectDepRS := projectDependency.GetDependency( + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) + if project != nil { + projectID = ptr.Deref(project.Status.ID, "") + } + } + + var addressScopeID string + if resource.AddressScopeRef != nil { + addressScope, addressScopeDepRS := addressScopeDependency.GetDependency( + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(addressScopeDepRS) + if addressScope != nil { + addressScopeID = ptr.Deref(addressScope.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := subnetpools.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + ProjectID: projectID, + AddressScopeID: addressScopeID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateSubnetPool(ctx, createOpts) + if err != nil { + 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 subnetpoolActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteSubnetPool(ctx, resource.ID)) +} + +func (actuator subnetpoolActuator) 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 := subnetpools.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.UpdateSubnetPool(ctx, osResource.ID, updateOpts) + + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts subnetpools.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToSubnetPoolUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["subnet_pool"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *subnetpools.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *subnetpools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator subnetpoolActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type subnetpoolHelperFactory struct{} + +var _ helperFactory = subnetpoolHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.SubnetPool, controller interfaces.ResourceController) (subnetpoolActuator, 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 subnetpoolActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return subnetpoolActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewSubnetPoolClient() + if err != nil { + return subnetpoolActuator{}, progress.WrapError(err) + } + + return subnetpoolActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (subnetpoolHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return subnetpoolAdapter{obj} +} + +func (subnetpoolHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (subnetpoolHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/subnetpool/actuator_test.go b/internal/controllers/subnetpool/actuator_test.go new file mode 100644 index 000000000..ef7cfcce0 --- /dev/null +++ b/internal/controllers/subnetpool/actuator_test.go @@ -0,0 +1,119 @@ +/* +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 subnetpool + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools" + 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 subnetpools.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: subnetpools.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: subnetpools.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.SubnetPool{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.SubnetPoolSpec{ + Resource: &orcv1alpha1.SubnetPoolResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := subnetpools.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.SubnetPoolResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := subnetpools.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/subnetpool/controller.go b/internal/controllers/subnetpool/controller.go new file mode 100644 index 000000000..49fce022d --- /dev/null +++ b/internal/controllers/subnetpool/controller.go @@ -0,0 +1,156 @@ +/* +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 subnetpool + +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 = "subnetpool" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=subnetpools,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=subnetpools/status,verbs=get;update;patch + +type subnetpoolReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return subnetpoolReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (subnetpoolReconcilerConstructor) GetName() string { + return controllerName +} + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.SubnetPoolList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(subnetpool *orcv1alpha1.SubnetPool) []string { + resource := subnetpool.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var addressScopeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.SubnetPoolList, *orcv1alpha1.AddressScope]( + "spec.resource.addressScopeRef", + func(subnetpool *orcv1alpha1.SubnetPool) []string { + resource := subnetpool.Spec.Resource + if resource == nil || resource.AddressScopeRef == nil { + return nil + } + return []string{string(*resource.AddressScopeRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.SubnetPoolList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(subnetpool *orcv1alpha1.SubnetPool) []string { + resource := subnetpool.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +var addressScopeImportDependency = dependency.NewDependency[*orcv1alpha1.SubnetPoolList, *orcv1alpha1.AddressScope]( + "spec.import.filter.addressScopeRef", + func(subnetpool *orcv1alpha1.SubnetPool) []string { + resource := subnetpool.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.AddressScopeRef == nil { + return nil + } + return []string{string(*resource.Filter.AddressScopeRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c subnetpoolReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + addressScopeWatchEventHandler, err := addressScopeDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + addressScopeImportWatchEventHandler, err := addressScopeImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + Watches(&orcv1alpha1.AddressScope{}, addressScopeWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.AddressScope{})), + ). + // 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.AddressScope{}, addressScopeImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.AddressScope{})), + ). + For(&orcv1alpha1.SubnetPool{}) + + if err := errors.Join( + projectDependency.AddToManager(ctx, mgr), + addressScopeDependency.AddToManager(ctx, mgr), + projectImportDependency.AddToManager(ctx, mgr), + addressScopeImportDependency.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, subnetpoolHelperFactory{}, subnetpoolStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/subnetpool/status.go b/internal/controllers/subnetpool/status.go new file mode 100644 index 000000000..7cfd6c22a --- /dev/null +++ b/internal/controllers/subnetpool/status.go @@ -0,0 +1,65 @@ +/* +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 subnetpool + +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 subnetpoolStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.SubnetPoolApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.SubnetPoolStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.SubnetPool, *osResourceT, *objectApplyT, *statusApplyT] = subnetpoolStatusWriter{} + +func (subnetpoolStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.SubnetPool(name, namespace) +} + +func (subnetpoolStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.SubnetPool, 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 (subnetpoolStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.SubnetPoolResourceStatus(). + WithProjectID(osResource.ProjectID). + WithAddressScopeID(osResource.AddressScopeID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the SubnetPoolResourceStatus 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/subnetpool/tests/subnetpool-create-full/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml new file mode 100644 index 000000000..46a178391 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-create-full +status: + resource: + name: subnetpool-create-full-override + description: SubnetPool 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: SubnetPool + name: subnetpool-create-full + ref: subnetpool + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: subnetpool-create-full + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: subnetpool-create-full + ref: addressScope +assertAll: + - celExpr: "subnetpool.status.id != ''" + - celExpr: "subnetpool.status.resource.projectID == project.status.id" + - celExpr: "subnetpool.status.resource.addressScopeID == addressScope.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml new file mode 100644 index 000000000..5537dd62a --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: subnetpool-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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: AddressScope +metadata: + name: subnetpool-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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: SubnetPool +metadata: + name: subnetpool-create-full +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: subnetpool-create-full-override + description: SubnetPool from "create full" test + projectRef: subnetpool-create-full + addressScopeRef: subnetpool-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-full/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-create-full/README.md b/internal/controllers/subnetpool/tests/subnetpool-create-full/README.md new file mode 100644 index 000000000..8417e6f4c --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-create-full/README.md @@ -0,0 +1,11 @@ +# Create a SubnetPool with all the options + +## Step 00 + +Create a SubnetPool 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/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml new file mode 100644 index 000000000..4ba550402 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-create-minimal +status: + resource: + name: subnetpool-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: SubnetPool + name: subnetpool-create-minimal + ref: subnetpool +assertAll: + - celExpr: "subnetpool.status.id != ''" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..f509d8edf --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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/subnetpool/tests/subnetpool-create-minimal/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-create-minimal/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/01-assert.yaml new file mode 100644 index 000000000..f79ffcfb9 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool' in secret.metadata.finalizers" diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-minimal/01-delete-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-create-minimal/README.md b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/README.md new file mode 100644 index 000000000..81a800c0c --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a SubnetPool with the minimum options + +## Step 00 + +Create a minimal SubnetPool, 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/subnetpool/tests/subnetpool-dependency/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-assert.yaml new file mode 100644 index 000000000..6b3795a60 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/subnetpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/subnetpool-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/subnetpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/subnetpool-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-addressscope +status: + conditions: + - type: Available + message: Waiting for AddressScope/subnetpool-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for AddressScope/subnetpool-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..af4550327 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-project +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + projectRef: subnetpool-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-addressscope +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + addressScopeRef: subnetpool-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: subnetpool-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-dependency/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/01-assert.yaml new file mode 100644 index 000000000..e40d8acf3 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/01-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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: SubnetPool +metadata: + name: subnetpool-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 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-dependency-no-addressscope +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/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..d39d579fa --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic subnetpool-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: subnetpool-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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: AddressScope +metadata: + name: subnetpool-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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/subnetpool/tests/subnetpool-dependency/02-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/02-assert.yaml new file mode 100644 index 000000000..81b68de1c --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/02-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: subnetpool-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: subnetpool-dependency + ref: addressScope + - apiVersion: v1 + kind: Secret + name: subnetpool-dependency + ref: secret +assertAll: + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/subnetpool' in project.metadata.finalizers" + - celExpr: "addressScope.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/subnetpool' in addressScope.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/subnetpool' in secret.metadata.finalizers" diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/02-delete-dependencies.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..67c0843d3 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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 project.openstack.k-orc.cloud subnetpool-dependency --wait=false + namespaced: true + - command: kubectl delete addressscope.openstack.k-orc.cloud subnetpool-dependency --wait=false + namespaced: true + - command: kubectl delete secret subnetpool-dependency --wait=false + namespaced: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/03-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/03-assert.yaml new file mode 100644 index 000000000..64848c23e --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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 project.openstack.k-orc.cloud subnetpool-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get addressscope.openstack.k-orc.cloud subnetpool-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret subnetpool-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/03-delete-resources.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..e59c6326a --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/03-delete-resources.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-dependency-no-project +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-dependency-no-addressscope diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/README.md b/internal/controllers/subnetpool/tests/subnetpool-dependency/README.md new file mode 100644 index 000000000..3a7a5fcbf --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create SubnetPools referencing non-existing resources. Each SubnetPool is dependent on other non-existing resource. Verify that the SubnetPools are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the SubnetPools 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 SubnetPools and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-assert.yaml new file mode 100644 index 000000000..3f1e47d95 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-assert.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Project/subnetpool-import-dependency to be ready + Waiting for AddressScope/subnetpool-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Project/subnetpool-import-dependency to be ready + Waiting for AddressScope/subnetpool-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..71d31b1c5 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: subnetpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: subnetpool-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: subnetpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: subnetpool-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + projectRef: subnetpool-import-dependency + addressScopeRef: subnetpool-import-dependency diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-import-dependency/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-assert.yaml new file mode 100644 index 000000000..9733da4e6 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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: SubnetPool +metadata: + name: subnetpool-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Project/subnetpool-import-dependency to be ready + Waiting for AddressScope/subnetpool-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Project/subnetpool-import-dependency to be ready + Waiting for AddressScope/subnetpool-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..17f7d5a05 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: subnetpool-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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: AddressScope +metadata: + name: subnetpool-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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 `subnetpool-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + projectRef: subnetpool-import-dependency-not-this-one + addressScopeRef: subnetpool-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-assert.yaml new file mode 100644 index 000000000..959f27d17 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-assert.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-import-dependency + ref: subnetpool1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-import-dependency-not-this-one + ref: subnetpool2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: subnetpool-import-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: subnetpool-import-dependency + ref: addressScope +assertAll: + - celExpr: "subnetpool1.status.id != subnetpool2.status.id" + - celExpr: "subnetpool1.status.resource.projectID == project.status.id" + - celExpr: "subnetpool1.status.resource.addressScopeID == addressScope.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..29aafe5e6 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: subnetpool-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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: AddressScope +metadata: + name: subnetpool-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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: SubnetPool +metadata: + name: subnetpool-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + projectRef: subnetpool-import-dependency-external + addressScopeRef: subnetpool-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-assert.yaml new file mode 100644 index 000000000..106c0761d --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get project.openstack.k-orc.cloud subnetpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get addressscope.openstack.k-orc.cloud subnetpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..db6708813 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete project.openstack.k-orc.cloud subnetpool-import-dependency + namespaced: true + - command: kubectl delete addressscope.openstack.k-orc.cloud subnetpool-import-dependency + namespaced: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-assert.yaml new file mode 100644 index 000000000..10b8fe016 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get subnetpool.openstack.k-orc.cloud subnetpool-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-delete-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..23f809d2f --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-import-dependency diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/README.md b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/README.md new file mode 100644 index 000000000..e58429fba --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported SubnetPool + +## Step 00 + +Import a SubnetPool that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the SubnetPool is waiting for the dependency to be ready. + +## Step 01 + +Create a SubnetPool matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a SubnetPool matching the import filters. + +Verify that the observed status on the imported SubnetPool corresponds to the spec of the created SubnetPool. + +## 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 SubnetPool and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-error/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-assert.yaml new file mode 100644 index 000000000..9eee57ee6 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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: SubnetPool +metadata: + name: subnetpool-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/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml new file mode 100644 index 000000000..f66b14477 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-error-external-1 +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: SubnetPool from "import error" test + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-error-external-2 +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: SubnetPool from "import error" test + # TODO(scaffolding): add any required field diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-error/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-import-error/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-error/01-assert.yaml new file mode 100644 index 000000000..bcb0e2104 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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/subnetpool/tests/subnetpool-import-error/01-import-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-error/01-import-resource.yaml new file mode 100644 index 000000000..a91e9cbf5 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: SubnetPool from "import error" test diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-error/README.md b/internal/controllers/subnetpool/tests/subnetpool-import-error/README.md new file mode 100644 index 000000000..f59b2a79a --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import-error/README.md @@ -0,0 +1,13 @@ +# Import SubnetPool with more than one matching resources + +## Step 00 + +Create two SubnetPools with identical specs. + +## Step 01 + +Ensure that an imported SubnetPool with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/00-assert.yaml new file mode 100644 index 000000000..22212cdac --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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/subnetpool/tests/subnetpool-import/00-import-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml new file mode 100644 index 000000000..589fd6d12 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: subnetpool-import-external + description: SubnetPool subnetpool-import-external from "subnetpool-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-import/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml new file mode 100644 index 000000000..2fffffeb7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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: subnetpool-import-external-not-this-one + description: SubnetPool subnetpool-import-external from "subnetpool-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..ded2d524e --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml @@ -0,0 +1,17 @@ +--- +# This `subnetpool-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: SubnetPool +metadata: + name: subnetpool-import-external-not-this-one +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: SubnetPool subnetpool-import-external from "subnetpool-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml new file mode 100644 index 000000000..3945561d8 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-import-external + ref: subnetpool1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-import-external-not-this-one + ref: subnetpool2 +assertAll: + - celExpr: "subnetpool1.status.id != subnetpool2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-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: subnetpool-import-external + description: SubnetPool subnetpool-import-external from "subnetpool-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml new file mode 100644 index 000000000..bccca768e --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-import-external +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: SubnetPool subnetpool-import-external from "subnetpool-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/README.md b/internal/controllers/subnetpool/tests/subnetpool-import/README.md new file mode 100644 index 000000000..bf9ee1f88 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-import/README.md @@ -0,0 +1,18 @@ +# Import SubnetPool + +## Step 00 + +Import a subnetpool that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a subnetpool 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 subnetpool matching the filter and verify that the observed status on the imported subnetpool corresponds to the spec of the created subnetpool. +Also, confirm that it does not adopt any subnetpool whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml new file mode 100644 index 000000000..066103311 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-update + ref: subnetpool +assertAll: + - celExpr: "!has(subnetpool.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-update +status: + resource: + name: subnetpool-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/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml new file mode 100644 index 000000000..2ea6ac9e2 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource 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/subnetpool/tests/subnetpool-update/00-secret.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/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/subnetpool/tests/subnetpool-update/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml new file mode 100644 index 000000000..a1b1434f9 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-update +status: + resource: + name: subnetpool-update-updated + description: subnetpool-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/subnetpool/tests/subnetpool-update/01-updated-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml new file mode 100644 index 000000000..8a21ba7e4 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-update +spec: + resource: + name: subnetpool-update-updated + description: subnetpool-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml new file mode 100644 index 000000000..3742a7541 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-update + ref: subnetpool +assertAll: + - celExpr: "!has(subnetpool.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-update +status: + resource: + name: subnetpool-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/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-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/subnetpool/tests/subnetpool-update/README.md b/internal/controllers/subnetpool/tests/subnetpool-update/README.md new file mode 100644 index 000000000..6a9909325 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/README.md @@ -0,0 +1,17 @@ +# Update SubnetPool + +## Step 00 + +Create a SubnetPool 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/subnetpool.go b/internal/osclients/subnetpool.go new file mode 100644 index 000000000..601aa622b --- /dev/null +++ b/internal/osclients/subnetpool.go @@ -0,0 +1,104 @@ +/* +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 osclients + +import ( + "context" + "fmt" + "iter" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type SubnetPoolClient interface { + ListSubnetPools(ctx context.Context, listOpts subnetpools.ListOptsBuilder) iter.Seq2[*subnetpools.SubnetPool, error] + CreateSubnetPool(ctx context.Context, opts subnetpools.CreateOptsBuilder) (*subnetpools.SubnetPool, error) + DeleteSubnetPool(ctx context.Context, resourceID string) error + GetSubnetPool(ctx context.Context, resourceID string) (*subnetpools.SubnetPool, error) + UpdateSubnetPool(ctx context.Context, id string, opts subnetpools.UpdateOptsBuilder) (*subnetpools.SubnetPool, error) +} + +type subnetpoolClient struct{ client *gophercloud.ServiceClient } + +// NewSubnetPoolClient returns a new OpenStack client. +func NewSubnetPoolClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (SubnetPoolClient, error) { + client, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create subnetpool service client: %v", err) + } + + return &subnetpoolClient{client}, nil +} + +func (c subnetpoolClient) ListSubnetPools(ctx context.Context, listOpts subnetpools.ListOptsBuilder) iter.Seq2[*subnetpools.SubnetPool, error] { + pager := subnetpools.List(c.client, listOpts) + return func(yield func(*subnetpools.SubnetPool, error) bool) { + _ = pager.EachPage(ctx, yieldPage(subnetpools.ExtractSubnetPools, yield)) + } +} + +func (c subnetpoolClient) CreateSubnetPool(ctx context.Context, opts subnetpools.CreateOptsBuilder) (*subnetpools.SubnetPool, error) { + return subnetpools.Create(ctx, c.client, opts).Extract() +} + +func (c subnetpoolClient) DeleteSubnetPool(ctx context.Context, resourceID string) error { + return subnetpools.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c subnetpoolClient) GetSubnetPool(ctx context.Context, resourceID string) (*subnetpools.SubnetPool, error) { + return subnetpools.Get(ctx, c.client, resourceID).Extract() +} + +func (c subnetpoolClient) UpdateSubnetPool(ctx context.Context, id string, opts subnetpools.UpdateOptsBuilder) (*subnetpools.SubnetPool, error) { + return subnetpools.Update(ctx, c.client, id, opts).Extract() +} + +type subnetpoolErrorClient struct{ error } + +// NewSubnetPoolErrorClient returns a SubnetPoolClient in which every method returns the given error. +func NewSubnetPoolErrorClient(e error) SubnetPoolClient { + return subnetpoolErrorClient{e} +} + +func (e subnetpoolErrorClient) ListSubnetPools(_ context.Context, _ subnetpools.ListOptsBuilder) iter.Seq2[*subnetpools.SubnetPool, error] { + return func(yield func(*subnetpools.SubnetPool, error) bool) { + yield(nil, e.error) + } +} + +func (e subnetpoolErrorClient) CreateSubnetPool(_ context.Context, _ subnetpools.CreateOptsBuilder) (*subnetpools.SubnetPool, error) { + return nil, e.error +} + +func (e subnetpoolErrorClient) DeleteSubnetPool(_ context.Context, _ string) error { + return e.error +} + +func (e subnetpoolErrorClient) GetSubnetPool(_ context.Context, _ string) (*subnetpools.SubnetPool, error) { + return nil, e.error +} + +func (e subnetpoolErrorClient) UpdateSubnetPool(_ context.Context, _ string, _ subnetpools.UpdateOptsBuilder) (*subnetpools.SubnetPool, error) { + return nil, e.error +} diff --git a/test/apivalidations/subnetpool_test.go b/test/apivalidations/subnetpool_test.go new file mode 100644 index 000000000..d20ebdf6b --- /dev/null +++ b/test/apivalidations/subnetpool_test.go @@ -0,0 +1,132 @@ +/* +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 apivalidations + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +const ( + subnetpoolName = "subnetpool" + subnetpoolID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func subnetpoolStub(namespace *corev1.Namespace) *orcv1alpha1.SubnetPool { + obj := &orcv1alpha1.SubnetPool{} + obj.Name = subnetpoolName + obj.Namespace = namespace.Name + return obj +} + +func testSubnetPoolResource() *applyconfigv1alpha1.SubnetPoolResourceSpecApplyConfiguration { + return applyconfigv1alpha1.SubnetPoolResourceSpec() +} + +func baseSubnetPoolPatch(obj client.Object) *applyconfigv1alpha1.SubnetPoolApplyConfiguration { + return applyconfigv1alpha1.SubnetPool(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.SubnetPoolSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testSubnetPoolImport() *applyconfigv1alpha1.SubnetPoolImportApplyConfiguration { + return applyconfigv1alpha1.SubnetPoolImport().WithID(subnetpoolID) +} + +var _ = Describe("ORC SubnetPool API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.SubnetPoolApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return subnetpoolStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.SubnetPoolApplyConfiguration { + return baseSubnetPoolPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithResource(testSubnetPoolResource()) + }, + applyImport: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithImport(testSubnetPoolImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SubnetPoolImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SubnetPoolImport().WithFilter(applyconfigv1alpha1.SubnetPoolFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SubnetPoolImport().WithFilter(applyconfigv1alpha1.SubnetPoolFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.SubnetPoolApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.SubnetPool).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.SubnetPool).Spec.ManagedOptions.OnDelete + }, + }) + + It("should have immutable projectRef", func(ctx context.Context) { + obj := subnetpoolStub(namespace) + patch := baseSubnetPoolPatch(obj) + patch.Spec.WithResource(testSubnetPoolResource(). + WithProjectRef("project-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testSubnetPoolResource(). + WithProjectRef("project-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should have immutable addressScopeRef", func(ctx context.Context) { + obj := subnetpoolStub(namespace) + patch := baseSubnetPoolPatch(obj) + patch.Spec.WithResource(testSubnetPoolResource(). + WithAddressScopeRef("addressscope-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testSubnetPoolResource(). + WithAddressScopeRef("addressscope-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("addressScopeRef is immutable"))) + }) + + // TODO(scaffolding): Add more resource-specific validation tests. + // Some common things to test: + // - Immutability of fields with `self == oldSelf` validation + // - Enum validation (valid and invalid values) + // - Numeric range validation (min/max bounds) + // - Tag uniqueness (if the resource has tags with listType=set) + // - Format validation (CIDR, UUID, etc.) + // - Cross-field validation rules +}) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 3cfdb8ac9..dc0f9b9cb 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2232,6 +2232,8 @@ _Appears in:_ - [ServerVolumeSpec](#servervolumespec) - [ShareNetworkResourceSpec](#sharenetworkresourcespec) - [SubnetFilter](#subnetfilter) +- [SubnetPoolFilter](#subnetpoolfilter) +- [SubnetPoolResourceSpec](#subnetpoolresourcespec) - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) - [TrunkResourceSpec](#trunkresourcespec) @@ -2657,6 +2659,8 @@ _Appears in:_ - [ShareNetworkFilter](#sharenetworkfilter) - [ShareNetworkResourceSpec](#sharenetworkresourcespec) - [SubnetFilter](#subnetfilter) +- [SubnetPoolFilter](#subnetpoolfilter) +- [SubnetPoolResourceSpec](#subnetpoolresourcespec) - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) - [TrunkResourceSpec](#trunkresourcespec) @@ -4724,6 +4728,12 @@ _Appears in:_ | `filter` _[SubnetFilter](#subnetfilter)_ | 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
Optional: \{\}
| + + + + + + #### SubnetResourceSpec From a0438ff5d16fd5b906b42cf2031d3894b5d6a769 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 13 May 2026 14:14:52 +0100 Subject: [PATCH 2/4] subnetpool: address post-scaffolding steps --- PROJECT | 8 + api/v1alpha1/zz_generated.deepcopy.go | 147 ++++++++ .../zz_generated.subnetpool-resource.go | 179 ++++++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 235 +++++++++++++ cmd/resource-generator/main.go | 8 +- .../openstack.k-orc.cloud_subnetpools.yaml | 329 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + .../subnetpool/zz_generated.adapter.go | 88 +++++ .../subnetpool/zz_generated.controller.go | 45 +++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/subnetpool.go | 131 +++++++ internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/subnetpool.go | 281 +++++++++++++++ .../api/v1alpha1/subnetpoolfilter.go | 70 ++++ .../api/v1alpha1/subnetpoolimport.go | 48 +++ .../api/v1alpha1/subnetpoolresourcespec.go | 70 ++++ .../api/v1alpha1/subnetpoolresourcestatus.go | 66 ++++ .../api/v1alpha1/subnetpoolspec.go | 79 +++++ .../api/v1alpha1/subnetpoolstatus.go | 66 ++++ .../applyconfiguration/internal/internal.go | 111 ++++++ 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_subnetpool.go | 51 +++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../typed/api/v1alpha1/subnetpool.go | 74 ++++ .../api/v1alpha1/interface.go | 7 + .../api/v1alpha1/subnetpool.go | 102 ++++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + .../listers/api/v1alpha1/subnetpool.go | 70 ++++ website/docs/crd-reference.md | 133 +++++++ 37 files changed, 2451 insertions(+), 2 deletions(-) create mode 100644 api/v1alpha1/zz_generated.subnetpool-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml create mode 100644 internal/controllers/subnetpool/zz_generated.adapter.go create mode 100644 internal/controllers/subnetpool/zz_generated.controller.go create mode 100644 internal/osclients/mock/subnetpool.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpool.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnetpool.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/subnetpool.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/subnetpool.go create mode 100644 pkg/clients/listers/api/v1alpha1/subnetpool.go diff --git a/PROJECT b/PROJECT index 357849779..e4dc0a8fb 100644 --- a/PROJECT +++ b/PROJECT @@ -184,6 +184,14 @@ resources: kind: Subnet path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: SubnetPool + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8abd3f2fb..12c626fe1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6455,6 +6455,33 @@ func (in *SubnetList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPool) DeepCopyInto(out *SubnetPool) { + *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 SubnetPool. +func (in *SubnetPool) DeepCopy() *SubnetPool { + if in == nil { + return nil + } + out := new(SubnetPool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetPool) 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 *SubnetPoolFilter) DeepCopyInto(out *SubnetPoolFilter) { *out = *in @@ -6490,6 +6517,63 @@ func (in *SubnetPoolFilter) DeepCopy() *SubnetPoolFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolImport) DeepCopyInto(out *SubnetPoolImport) { + *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(SubnetPoolFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolImport. +func (in *SubnetPoolImport) DeepCopy() *SubnetPoolImport { + if in == nil { + return nil + } + out := new(SubnetPoolImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolList) DeepCopyInto(out *SubnetPoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SubnetPool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolList. +func (in *SubnetPoolList) DeepCopy() *SubnetPoolList { + if in == nil { + return nil + } + out := new(SubnetPoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubnetPoolList) 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 *SubnetPoolResourceSpec) DeepCopyInto(out *SubnetPoolResourceSpec) { *out = *in @@ -6540,6 +6624,69 @@ func (in *SubnetPoolResourceStatus) DeepCopy() *SubnetPoolResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolSpec) DeepCopyInto(out *SubnetPoolSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(SubnetPoolImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(SubnetPoolResourceSpec) + (*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 SubnetPoolSpec. +func (in *SubnetPoolSpec) DeepCopy() *SubnetPoolSpec { + if in == nil { + return nil + } + out := new(SubnetPoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubnetPoolStatus) DeepCopyInto(out *SubnetPoolStatus) { + *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(SubnetPoolResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolStatus. +func (in *SubnetPoolStatus) DeepCopy() *SubnetPoolStatus { + if in == nil { + return nil + } + out := new(SubnetPoolStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubnetResourceSpec) DeepCopyInto(out *SubnetResourceSpec) { *out = *in diff --git a/api/v1alpha1/zz_generated.subnetpool-resource.go b/api/v1alpha1/zz_generated.subnetpool-resource.go new file mode 100644 index 000000000..fb592254a --- /dev/null +++ b/api/v1alpha1/zz_generated.subnetpool-resource.go @@ -0,0 +1,179 @@ +// 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" +) + +// SubnetPoolImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type SubnetPoolImport 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. + // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional + ID *string `json:"id,omitempty"` //nolint:kubeapilinter + + // 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 *SubnetPoolFilter `json:"filter,omitempty"` +} + +// SubnetPoolSpec 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 SubnetPoolSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *SubnetPoolImport `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 *SubnetPoolResourceSpec `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,omitzero"` +} + +// SubnetPoolStatus defines the observed state of an ORC resource. +type SubnetPoolStatus 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. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *SubnetPoolResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &SubnetPool{} + +func (i *SubnetPool) 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" + +// SubnetPool is the Schema for an ORC resource. +type SubnetPool struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +required + Spec SubnetPoolSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status SubnetPoolStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// SubnetPoolList contains a list of SubnetPool. +type SubnetPoolList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of SubnetPool. + // +required + Items []SubnetPool `json:"items"` +} + +func (l *SubnetPoolList) GetItems() []SubnetPool { + return l.Items +} + +func init() { + SchemeBuilder.Register(&SubnetPool{}, &SubnetPoolList{}) +} + +func (i *SubnetPool) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &SubnetPool{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 6f759c2e5..5f7460f6e 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -50,6 +50,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/service" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/sharenetwork" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/subnet" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/subnetpool" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/trunk" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/user" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volume" @@ -132,6 +133,7 @@ func main() { securitygroup.New(scopeFactory), server.New(scopeFactory), servergroup.New(scopeFactory), + subnetpool.New(scopeFactory), project.New(scopeFactory), user.New(scopeFactory), volume.New(scopeFactory), diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index cb2417821..046e87c17 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -246,9 +246,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetImport(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPool": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPool(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolList(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), @@ -12014,6 +12019,57 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref common. } } +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPool(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPool 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.SubnetPoolSpec"), + }, + }, + "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.SubnetPoolStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -12055,6 +12111,85 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolFilter(ref c } } +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolImport 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.SubnetPoolFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolList contains a list of SubnetPool.", + 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 SubnetPool.", + 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.SubnetPool"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPool", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -12137,6 +12272,106 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceStat } } +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolSpec 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.SubnetPoolImport"), + }, + }, + "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.SubnetPoolResourceSpec"), + }, + }, + "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.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubnetPoolStatus 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.SubnetPoolResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index c5d6bc042..4fcb369b7 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -189,6 +189,9 @@ var resources []templateFields = []templateFields{ { Name: "ApplicationCredential", }, + { + Name: "SubnetPool", + }, } // These resources won't be generated @@ -207,7 +210,8 @@ func main() { kuttlTestTemplate := template.Must(template.New("kuttl-test").Parse(kuttl_test_template)) crdKustomizationTemplate := template.Must(template.New("crd-kustomization").Parse(crd_kustomization_template)) samplesKustomizationTemplate := template.Must( - template.New("samples-kustomization").Parse(samples_kustomization_template)) + template.New("samples-kustomization").Parse(samples_kustomization_template), + ) mockDocTemplate := template.Must(template.New("mock-doc").Parse(mock_doc_template)) addDefaults(resources) @@ -223,7 +227,7 @@ func main() { controllerDirPath := filepath.Join("internal", "controllers", resource.NameLower) if _, err := os.Stat(controllerDirPath); os.IsNotExist(err) { - err = os.Mkdir(controllerDirPath, 0755) + err = os.Mkdir(controllerDirPath, 0o755) if err != nil { panic(err) } diff --git a/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml b/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml new file mode 100644 index 000000000..8285f441d --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml @@ -0,0 +1,329 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: subnetpools.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: SubnetPool + listKind: SubnetPoolList + plural: subnetpools + singular: subnetpool + 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: SubnetPool 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: + addressScopeRef: + description: addressScopeRef is a reference to the ORC AddressScope + which this resource is associated with. + maxLength: 253 + minLength: 1 + 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 + projectRef: + description: projectRef is a reference to the ORC Project + which this resource is associated with. + 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 + maxLength: 36 + 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: + addressScopeRef: + description: addressScopeRef is a reference to the ORC AddressScope + which this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: addressScopeRef is immutable + rule: self == oldSelf + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + 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 + 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. + maxLength: 1024 + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + addressScopeID: + description: addressScopeID is the ID of the AddressScope to which + the resource is associated. + maxLength: 1024 + type: string + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + name: + description: name is a Human-readable name for the resource. Might + not be unique. + maxLength: 1024 + type: string + projectID: + description: projectID is the ID of the Project to which the resource + is associated. + maxLength: 1024 + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 9c133d11c..ad643f647 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -25,6 +25,7 @@ resources: - bases/openstack.k-orc.cloud_services.yaml - bases/openstack.k-orc.cloud_sharenetworks.yaml - bases/openstack.k-orc.cloud_subnets.yaml +- bases/openstack.k-orc.cloud_subnetpools.yaml - bases/openstack.k-orc.cloud_trunks.yaml - bases/openstack.k-orc.cloud_users.yaml - bases/openstack.k-orc.cloud_volumes.yaml diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index ceb05e15e..848ebfb34 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -23,6 +23,7 @@ resources: - openstack_v1alpha1_service.yaml - openstack_v1alpha1_sharenetwork.yaml - openstack_v1alpha1_subnet.yaml +- openstack_v1alpha1_subnetpool.yaml - openstack_v1alpha1_trunk.yaml - openstack_v1alpha1_user.yaml - openstack_v1alpha1_volume.yaml diff --git a/internal/controllers/subnetpool/zz_generated.adapter.go b/internal/controllers/subnetpool/zz_generated.adapter.go new file mode 100644 index 000000000..31912efdf --- /dev/null +++ b/internal/controllers/subnetpool/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 subnetpool + +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.SubnetPool + orcObjectListT = orcv1alpha1.SubnetPoolList + resourceSpecT = orcv1alpha1.SubnetPoolResourceSpec + filterT = orcv1alpha1.SubnetPoolFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = subnetpoolAdapter +) + +type subnetpoolAdapter struct { + *orcv1alpha1.SubnetPool +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.SubnetPool +} + +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/subnetpool/zz_generated.controller.go b/internal/controllers/subnetpool/zz_generated.controller.go new file mode 100644 index 000000000..fb70e1b8f --- /dev/null +++ b/internal/controllers/subnetpool/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 subnetpool + +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 466a548e6..5125ce45f 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -65,6 +65,9 @@ import ( //go:generate mockgen -package mock -destination=sharenetwork.go -source=../sharenetwork.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ShareNetworkClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt sharenetwork.go > _sharenetwork.go && mv _sharenetwork.go sharenetwork.go" +//go:generate mockgen -package mock -destination=subnetpool.go -source=../subnetpool.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock SubnetPoolClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt subnetpool.go > _subnetpool.go && mv _subnetpool.go subnetpool.go" + //go:generate mockgen -package mock -destination=user.go -source=../user.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock UserClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt user.go > _user.go && mv _user.go user.go" diff --git a/internal/osclients/mock/subnetpool.go b/internal/osclients/mock/subnetpool.go new file mode 100644 index 000000000..f6c687c72 --- /dev/null +++ b/internal/osclients/mock/subnetpool.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: ../subnetpool.go +// +// Generated by this command: +// +// mockgen -package mock -destination=subnetpool.go -source=../subnetpool.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock SubnetPoolClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + subnetpools "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools" + gomock "go.uber.org/mock/gomock" +) + +// MockSubnetPoolClient is a mock of SubnetPoolClient interface. +type MockSubnetPoolClient struct { + ctrl *gomock.Controller + recorder *MockSubnetPoolClientMockRecorder + isgomock struct{} +} + +// MockSubnetPoolClientMockRecorder is the mock recorder for MockSubnetPoolClient. +type MockSubnetPoolClientMockRecorder struct { + mock *MockSubnetPoolClient +} + +// NewMockSubnetPoolClient creates a new mock instance. +func NewMockSubnetPoolClient(ctrl *gomock.Controller) *MockSubnetPoolClient { + mock := &MockSubnetPoolClient{ctrl: ctrl} + mock.recorder = &MockSubnetPoolClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubnetPoolClient) EXPECT() *MockSubnetPoolClientMockRecorder { + return m.recorder +} + +// CreateSubnetPool mocks base method. +func (m *MockSubnetPoolClient) CreateSubnetPool(ctx context.Context, opts subnetpools.CreateOptsBuilder) (*subnetpools.SubnetPool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSubnetPool", ctx, opts) + ret0, _ := ret[0].(*subnetpools.SubnetPool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSubnetPool indicates an expected call of CreateSubnetPool. +func (mr *MockSubnetPoolClientMockRecorder) CreateSubnetPool(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSubnetPool", reflect.TypeOf((*MockSubnetPoolClient)(nil).CreateSubnetPool), ctx, opts) +} + +// DeleteSubnetPool mocks base method. +func (m *MockSubnetPoolClient) DeleteSubnetPool(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSubnetPool", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSubnetPool indicates an expected call of DeleteSubnetPool. +func (mr *MockSubnetPoolClientMockRecorder) DeleteSubnetPool(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSubnetPool", reflect.TypeOf((*MockSubnetPoolClient)(nil).DeleteSubnetPool), ctx, resourceID) +} + +// GetSubnetPool mocks base method. +func (m *MockSubnetPoolClient) GetSubnetPool(ctx context.Context, resourceID string) (*subnetpools.SubnetPool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSubnetPool", ctx, resourceID) + ret0, _ := ret[0].(*subnetpools.SubnetPool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSubnetPool indicates an expected call of GetSubnetPool. +func (mr *MockSubnetPoolClientMockRecorder) GetSubnetPool(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetPool", reflect.TypeOf((*MockSubnetPoolClient)(nil).GetSubnetPool), ctx, resourceID) +} + +// ListSubnetPools mocks base method. +func (m *MockSubnetPoolClient) ListSubnetPools(ctx context.Context, listOpts subnetpools.ListOptsBuilder) iter.Seq2[*subnetpools.SubnetPool, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListSubnetPools", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*subnetpools.SubnetPool, error]) + return ret0 +} + +// ListSubnetPools indicates an expected call of ListSubnetPools. +func (mr *MockSubnetPoolClientMockRecorder) ListSubnetPools(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSubnetPools", reflect.TypeOf((*MockSubnetPoolClient)(nil).ListSubnetPools), ctx, listOpts) +} + +// UpdateSubnetPool mocks base method. +func (m *MockSubnetPoolClient) UpdateSubnetPool(ctx context.Context, id string, opts subnetpools.UpdateOptsBuilder) (*subnetpools.SubnetPool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSubnetPool", ctx, id, opts) + ret0, _ := ret[0].(*subnetpools.SubnetPool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateSubnetPool indicates an expected call of UpdateSubnetPool. +func (mr *MockSubnetPoolClientMockRecorder) UpdateSubnetPool(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSubnetPool", reflect.TypeOf((*MockSubnetPoolClient)(nil).UpdateSubnetPool), ctx, id, opts) +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index e57e4da58..8c4441e47 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -47,6 +47,7 @@ type MockScopeFactory struct { RoleClient *mock.MockRoleClient RoleAssignmentClient *mock.MockRoleAssignmentClient ServiceClient *mock.MockServiceClient + SubnetPoolClient *mock.MockSubnetPoolClient UserClient *mock.MockUserClient VolumeClient *mock.MockVolumeClient VolumeTypeClient *mock.MockVolumeTypeClient @@ -69,6 +70,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { roleClient := mock.NewMockRoleClient(mockCtrl) roleassignmentClient := mock.NewMockRoleAssignmentClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) + subnetPoolClient := mock.NewMockSubnetPoolClient(mockCtrl) userClient := mock.NewMockUserClient(mockCtrl) sharenetworkClient := mock.NewMockShareNetworkClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) @@ -89,6 +91,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { RoleAssignmentClient: roleassignmentClient, ServiceClient: serviceClient, ShareNetworkClient: sharenetworkClient, + SubnetPoolClient: subnetPoolClient, UserClient: userClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, @@ -150,6 +153,10 @@ func (f *MockScopeFactory) NewShareNetworkClient() (osclients.ShareNetworkClient return f.ShareNetworkClient, nil } +func (f *MockScopeFactory) NewSubnetPoolClient() (osclients.SubnetPoolClient, error) { + return f.SubnetPoolClient, nil +} + func (f *MockScopeFactory) NewKeyPairClient() (osclients.KeyPairClient, error) { return f.KeyPairClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index f1207bd6c..17a46d2da 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -181,6 +181,10 @@ func (s *providerScope) NewServiceClient() (clients.ServiceClient, error) { return clients.NewServiceClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewSubnetPoolClient() (clients.SubnetPoolClient, error) { + return clients.NewSubnetPoolClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) NewEndpointClient() (clients.EndpointClient, error) { return clients.NewEndpointClient(s.providerClient, s.providerClientOpts) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 4f093fe9a..be6946a34 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -62,6 +62,7 @@ type Scope interface { NewRoleAssignmentClient() (osclients.RoleAssignmentClient, error) NewServiceClient() (osclients.ServiceClient, error) NewShareNetworkClient() (osclients.ShareNetworkClient, error) + NewSubnetPoolClient() (osclients.SubnetPoolClient, error) NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 90fc98dde..16fda5d2e 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -24,6 +24,7 @@ testDirs: - ./internal/controllers/service/tests/ - ./internal/controllers/sharenetwork/tests/ - ./internal/controllers/subnet/tests/ +- ./internal/controllers/subnetpool/tests/ - ./internal/controllers/trunk/tests/ - ./internal/controllers/user/tests/ - ./internal/controllers/volume/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpool.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpool.go new file mode 100644 index 000000000..dbd410292 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpool.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" +) + +// SubnetPoolApplyConfiguration represents a declarative configuration of the SubnetPool type for use +// with apply. +type SubnetPoolApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *SubnetPoolSpecApplyConfiguration `json:"spec,omitempty"` + Status *SubnetPoolStatusApplyConfiguration `json:"status,omitempty"` +} + +// SubnetPool constructs a declarative configuration of the SubnetPool type for use with +// apply. +func SubnetPool(name, namespace string) *SubnetPoolApplyConfiguration { + b := &SubnetPoolApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("SubnetPool") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractSubnetPool extracts the applied configuration owned by fieldManager from +// subnetPool. If no managedFields are found in subnetPool for fieldManager, a +// SubnetPoolApplyConfiguration 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. +// subnetPool must be a unmodified SubnetPool API object that was retrieved from the Kubernetes API. +// ExtractSubnetPool 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 ExtractSubnetPool(subnetPool *apiv1alpha1.SubnetPool, fieldManager string) (*SubnetPoolApplyConfiguration, error) { + return extractSubnetPool(subnetPool, fieldManager, "") +} + +// ExtractSubnetPoolStatus is the same as ExtractSubnetPool except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractSubnetPoolStatus(subnetPool *apiv1alpha1.SubnetPool, fieldManager string) (*SubnetPoolApplyConfiguration, error) { + return extractSubnetPool(subnetPool, fieldManager, "status") +} + +func extractSubnetPool(subnetPool *apiv1alpha1.SubnetPool, fieldManager string, subresource string) (*SubnetPoolApplyConfiguration, error) { + b := &SubnetPoolApplyConfiguration{} + err := managedfields.ExtractInto(subnetPool, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPool"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(subnetPool.Name) + b.WithNamespace(subnetPool.Namespace) + + b.WithKind("SubnetPool") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b SubnetPoolApplyConfiguration) 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 *SubnetPoolApplyConfiguration) WithKind(value string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithAPIVersion(value string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithName(value string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithGenerateName(value string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithNamespace(value string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithUID(value types.UID) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithResourceVersion(value string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithGeneration(value int64) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithCreationTimestamp(value metav1.Time) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithLabels(entries map[string]string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithAnnotations(entries map[string]string) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithFinalizers(values ...string) *SubnetPoolApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *SubnetPoolApplyConfiguration) 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 *SubnetPoolApplyConfiguration) WithSpec(value *SubnetPoolSpecApplyConfiguration) *SubnetPoolApplyConfiguration { + 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 *SubnetPoolApplyConfiguration) WithStatus(value *SubnetPoolStatusApplyConfiguration) *SubnetPoolApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *SubnetPoolApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *SubnetPoolApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *SubnetPoolApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *SubnetPoolApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go new file mode 100644 index 000000000..ab1048834 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.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 applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// SubnetPoolFilterApplyConfiguration represents a declarative configuration of the SubnetPoolFilter type for use +// with apply. +type SubnetPoolFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AddressScopeRef *apiv1alpha1.KubernetesNameRef `json:"addressScopeRef,omitempty"` +} + +// SubnetPoolFilterApplyConfiguration constructs a declarative configuration of the SubnetPoolFilter type for use with +// apply. +func SubnetPoolFilter() *SubnetPoolFilterApplyConfiguration { + return &SubnetPoolFilterApplyConfiguration{} +} + +// 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 *SubnetPoolFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *SubnetPoolFilterApplyConfiguration { + 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 *SubnetPoolFilterApplyConfiguration) WithDescription(value string) *SubnetPoolFilterApplyConfiguration { + 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 *SubnetPoolFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *SubnetPoolFilterApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithAddressScopeRef sets the AddressScopeRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AddressScopeRef field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithAddressScopeRef(value apiv1alpha1.KubernetesNameRef) *SubnetPoolFilterApplyConfiguration { + b.AddressScopeRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolimport.go new file mode 100644 index 000000000..6af1f0ba1 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolimport.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 + +// SubnetPoolImportApplyConfiguration represents a declarative configuration of the SubnetPoolImport type for use +// with apply. +type SubnetPoolImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *SubnetPoolFilterApplyConfiguration `json:"filter,omitempty"` +} + +// SubnetPoolImportApplyConfiguration constructs a declarative configuration of the SubnetPoolImport type for use with +// apply. +func SubnetPoolImport() *SubnetPoolImportApplyConfiguration { + return &SubnetPoolImportApplyConfiguration{} +} + +// 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 *SubnetPoolImportApplyConfiguration) WithID(value string) *SubnetPoolImportApplyConfiguration { + 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 *SubnetPoolImportApplyConfiguration) WithFilter(value *SubnetPoolFilterApplyConfiguration) *SubnetPoolImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go new file mode 100644 index 000000000..5f9bdedc9 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.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 applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// SubnetPoolResourceSpecApplyConfiguration represents a declarative configuration of the SubnetPoolResourceSpec type for use +// with apply. +type SubnetPoolResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AddressScopeRef *apiv1alpha1.KubernetesNameRef `json:"addressScopeRef,omitempty"` +} + +// SubnetPoolResourceSpecApplyConfiguration constructs a declarative configuration of the SubnetPoolResourceSpec type for use with +// apply. +func SubnetPoolResourceSpec() *SubnetPoolResourceSpecApplyConfiguration { + return &SubnetPoolResourceSpecApplyConfiguration{} +} + +// 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 *SubnetPoolResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *SubnetPoolResourceSpecApplyConfiguration { + 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 *SubnetPoolResourceSpecApplyConfiguration) WithDescription(value string) *SubnetPoolResourceSpecApplyConfiguration { + 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 *SubnetPoolResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *SubnetPoolResourceSpecApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithAddressScopeRef sets the AddressScopeRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AddressScopeRef field is set to the value of the last call. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithAddressScopeRef(value apiv1alpha1.KubernetesNameRef) *SubnetPoolResourceSpecApplyConfiguration { + b.AddressScopeRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go new file mode 100644 index 000000000..738c26bee --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.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 + +// SubnetPoolResourceStatusApplyConfiguration represents a declarative configuration of the SubnetPoolResourceStatus type for use +// with apply. +type SubnetPoolResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + AddressScopeID *string `json:"addressScopeID,omitempty"` +} + +// SubnetPoolResourceStatusApplyConfiguration constructs a declarative configuration of the SubnetPoolResourceStatus type for use with +// apply. +func SubnetPoolResourceStatus() *SubnetPoolResourceStatusApplyConfiguration { + return &SubnetPoolResourceStatusApplyConfiguration{} +} + +// 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 *SubnetPoolResourceStatusApplyConfiguration) WithName(value string) *SubnetPoolResourceStatusApplyConfiguration { + 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 *SubnetPoolResourceStatusApplyConfiguration) WithDescription(value string) *SubnetPoolResourceStatusApplyConfiguration { + b.Description = &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 *SubnetPoolResourceStatusApplyConfiguration) WithProjectID(value string) *SubnetPoolResourceStatusApplyConfiguration { + b.ProjectID = &value + return b +} + +// WithAddressScopeID sets the AddressScopeID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AddressScopeID field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithAddressScopeID(value string) *SubnetPoolResourceStatusApplyConfiguration { + b.AddressScopeID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go new file mode 100644 index 000000000..2ec290331 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.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" +) + +// SubnetPoolSpecApplyConfiguration represents a declarative configuration of the SubnetPoolSpec type for use +// with apply. +type SubnetPoolSpecApplyConfiguration struct { + Import *SubnetPoolImportApplyConfiguration `json:"import,omitempty"` + Resource *SubnetPoolResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// SubnetPoolSpecApplyConfiguration constructs a declarative configuration of the SubnetPoolSpec type for use with +// apply. +func SubnetPoolSpec() *SubnetPoolSpecApplyConfiguration { + return &SubnetPoolSpecApplyConfiguration{} +} + +// 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 *SubnetPoolSpecApplyConfiguration) WithImport(value *SubnetPoolImportApplyConfiguration) *SubnetPoolSpecApplyConfiguration { + 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 *SubnetPoolSpecApplyConfiguration) WithResource(value *SubnetPoolResourceSpecApplyConfiguration) *SubnetPoolSpecApplyConfiguration { + 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 *SubnetPoolSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *SubnetPoolSpecApplyConfiguration { + 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 *SubnetPoolSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *SubnetPoolSpecApplyConfiguration { + 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 *SubnetPoolSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *SubnetPoolSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go new file mode 100644 index 000000000..ba58679ca --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.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" +) + +// SubnetPoolStatusApplyConfiguration represents a declarative configuration of the SubnetPoolStatus type for use +// with apply. +type SubnetPoolStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *SubnetPoolResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// SubnetPoolStatusApplyConfiguration constructs a declarative configuration of the SubnetPoolStatus type for use with +// apply. +func SubnetPoolStatus() *SubnetPoolStatusApplyConfiguration { + return &SubnetPoolStatusApplyConfiguration{} +} + +// 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 *SubnetPoolStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *SubnetPoolStatusApplyConfiguration { + 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 *SubnetPoolStatusApplyConfiguration) WithID(value string) *SubnetPoolStatusApplyConfiguration { + 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 *SubnetPoolStatusApplyConfiguration) WithResource(value *SubnetPoolResourceStatusApplyConfiguration) *SubnetPoolStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 9fc18f739..ba672e633 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3639,6 +3639,117 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPool + 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.SubnetPoolSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolFilter + map: + fields: + - name: addressScopeRef + type: + scalar: string + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolResourceSpec + map: + fields: + - name: addressScopeRef + type: + scalar: string + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolResourceStatus + map: + fields: + - name: addressScopeID + type: + scalar: string + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: projectID + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolSpec + 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.SubnetPoolImport + - 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.SubnetPoolResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolStatus + 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.SubnetPoolResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetResourceSpec map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 22e66aea0..58078d6bd 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -420,6 +420,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.SubnetGatewayApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetImport"): return &apiv1alpha1.SubnetImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPool"): + return &apiv1alpha1.SubnetPoolApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPoolFilter"): + return &apiv1alpha1.SubnetPoolFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPoolImport"): + return &apiv1alpha1.SubnetPoolImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPoolResourceSpec"): + return &apiv1alpha1.SubnetPoolResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPoolResourceStatus"): + return &apiv1alpha1.SubnetPoolResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPoolSpec"): + return &apiv1alpha1.SubnetPoolSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SubnetPoolStatus"): + return &apiv1alpha1.SubnetPoolStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetResourceSpec"): return &apiv1alpha1.SubnetResourceSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetResourceStatus"): 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 e39fc5dc9..b22e1e2fd 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -50,6 +50,7 @@ type OpenstackV1alpha1Interface interface { ServicesGetter ShareNetworksGetter SubnetsGetter + SubnetPoolsGetter TrunksGetter UsersGetter VolumesGetter @@ -149,6 +150,10 @@ func (c *OpenstackV1alpha1Client) Subnets(namespace string) SubnetInterface { return newSubnets(c, namespace) } +func (c *OpenstackV1alpha1Client) SubnetPools(namespace string) SubnetPoolInterface { + return newSubnetPools(c, namespace) +} + func (c *OpenstackV1alpha1Client) Trunks(namespace string) TrunkInterface { return newTrunks(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 5015aed35..8343fcfba 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 @@ -116,6 +116,10 @@ func (c *FakeOpenstackV1alpha1) Subnets(namespace string) v1alpha1.SubnetInterfa return newFakeSubnets(c, namespace) } +func (c *FakeOpenstackV1alpha1) SubnetPools(namespace string) v1alpha1.SubnetPoolInterface { + return newFakeSubnetPools(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Trunks(namespace string) v1alpha1.TrunkInterface { return newFakeTrunks(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnetpool.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnetpool.go new file mode 100644 index 000000000..6c9a0629e --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnetpool.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" +) + +// fakeSubnetPools implements SubnetPoolInterface +type fakeSubnetPools struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.SubnetPool, *v1alpha1.SubnetPoolList, *apiv1alpha1.SubnetPoolApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeSubnetPools(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.SubnetPoolInterface { + return &fakeSubnetPools{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.SubnetPool, *v1alpha1.SubnetPoolList, *apiv1alpha1.SubnetPoolApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("subnetpools"), + v1alpha1.SchemeGroupVersion.WithKind("SubnetPool"), + func() *v1alpha1.SubnetPool { return &v1alpha1.SubnetPool{} }, + func() *v1alpha1.SubnetPoolList { return &v1alpha1.SubnetPoolList{} }, + func(dst, src *v1alpha1.SubnetPoolList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.SubnetPoolList) []*v1alpha1.SubnetPool { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.SubnetPoolList, items []*v1alpha1.SubnetPool) { + 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 7c5d67d45..0bb200231 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -62,6 +62,8 @@ type ShareNetworkExpansion interface{} type SubnetExpansion interface{} +type SubnetPoolExpansion interface{} + type TrunkExpansion interface{} type UserExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnetpool.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnetpool.go new file mode 100644 index 000000000..36f102c55 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnetpool.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" +) + +// SubnetPoolsGetter has a method to return a SubnetPoolInterface. +// A group's client should implement this interface. +type SubnetPoolsGetter interface { + SubnetPools(namespace string) SubnetPoolInterface +} + +// SubnetPoolInterface has methods to work with SubnetPool resources. +type SubnetPoolInterface interface { + Create(ctx context.Context, subnetPool *apiv1alpha1.SubnetPool, opts v1.CreateOptions) (*apiv1alpha1.SubnetPool, error) + Update(ctx context.Context, subnetPool *apiv1alpha1.SubnetPool, opts v1.UpdateOptions) (*apiv1alpha1.SubnetPool, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, subnetPool *apiv1alpha1.SubnetPool, opts v1.UpdateOptions) (*apiv1alpha1.SubnetPool, 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.SubnetPool, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.SubnetPoolList, 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.SubnetPool, err error) + Apply(ctx context.Context, subnetPool *applyconfigurationapiv1alpha1.SubnetPoolApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.SubnetPool, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, subnetPool *applyconfigurationapiv1alpha1.SubnetPoolApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.SubnetPool, err error) + SubnetPoolExpansion +} + +// subnetPools implements SubnetPoolInterface +type subnetPools struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.SubnetPool, *apiv1alpha1.SubnetPoolList, *applyconfigurationapiv1alpha1.SubnetPoolApplyConfiguration] +} + +// newSubnetPools returns a SubnetPools +func newSubnetPools(c *OpenstackV1alpha1Client, namespace string) *subnetPools { + return &subnetPools{ + gentype.NewClientWithListAndApply[*apiv1alpha1.SubnetPool, *apiv1alpha1.SubnetPoolList, *applyconfigurationapiv1alpha1.SubnetPoolApplyConfiguration]( + "subnetpools", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.SubnetPool { return &apiv1alpha1.SubnetPool{} }, + func() *apiv1alpha1.SubnetPoolList { return &apiv1alpha1.SubnetPoolList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 69d2bdeaf..8542cf6bb 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -68,6 +68,8 @@ type Interface interface { ShareNetworks() ShareNetworkInformer // Subnets returns a SubnetInformer. Subnets() SubnetInformer + // SubnetPools returns a SubnetPoolInformer. + SubnetPools() SubnetPoolInformer // Trunks returns a TrunkInformer. Trunks() TrunkInformer // Users returns a UserInformer. @@ -199,6 +201,11 @@ func (v *version) Subnets() SubnetInformer { return &subnetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// SubnetPools returns a SubnetPoolInformer. +func (v *version) SubnetPools() SubnetPoolInformer { + return &subnetPoolInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Trunks returns a TrunkInformer. func (v *version) Trunks() TrunkInformer { return &trunkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/subnetpool.go b/pkg/clients/informers/externalversions/api/v1alpha1/subnetpool.go new file mode 100644 index 000000000..cf4f96556 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/subnetpool.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" +) + +// SubnetPoolInformer provides access to a shared informer and lister for +// SubnetPools. +type SubnetPoolInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.SubnetPoolLister +} + +type subnetPoolInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSubnetPoolInformer constructs a new informer for SubnetPool 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 NewSubnetPoolInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSubnetPoolInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSubnetPoolInformer constructs a new informer for SubnetPool 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 NewFilteredSubnetPoolInformer(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().SubnetPools(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SubnetPools(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SubnetPools(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().SubnetPools(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.SubnetPool{}, + resyncPeriod, + indexers, + ) +} + +func (f *subnetPoolInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSubnetPoolInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *subnetPoolInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.SubnetPool{}, f.defaultInformer) +} + +func (f *subnetPoolInformer) Lister() apiv1alpha1.SubnetPoolLister { + return apiv1alpha1.NewSubnetPoolLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index f58420886..833e0c310 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -97,6 +97,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().ShareNetworks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subnets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Subnets().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("subnetpools"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().SubnetPools().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("trunks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Trunks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("users"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 76fec1603..59fb24f24 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -194,6 +194,14 @@ type SubnetListerExpansion interface{} // SubnetNamespaceLister. type SubnetNamespaceListerExpansion interface{} +// SubnetPoolListerExpansion allows custom methods to be added to +// SubnetPoolLister. +type SubnetPoolListerExpansion interface{} + +// SubnetPoolNamespaceListerExpansion allows custom methods to be added to +// SubnetPoolNamespaceLister. +type SubnetPoolNamespaceListerExpansion interface{} + // TrunkListerExpansion allows custom methods to be added to // TrunkLister. type TrunkListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/subnetpool.go b/pkg/clients/listers/api/v1alpha1/subnetpool.go new file mode 100644 index 000000000..bd3b99f93 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/subnetpool.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" +) + +// SubnetPoolLister helps list SubnetPools. +// All objects returned here must be treated as read-only. +type SubnetPoolLister interface { + // List lists all SubnetPools in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.SubnetPool, err error) + // SubnetPools returns an object that can list and get SubnetPools. + SubnetPools(namespace string) SubnetPoolNamespaceLister + SubnetPoolListerExpansion +} + +// subnetPoolLister implements the SubnetPoolLister interface. +type subnetPoolLister struct { + listers.ResourceIndexer[*apiv1alpha1.SubnetPool] +} + +// NewSubnetPoolLister returns a new SubnetPoolLister. +func NewSubnetPoolLister(indexer cache.Indexer) SubnetPoolLister { + return &subnetPoolLister{listers.New[*apiv1alpha1.SubnetPool](indexer, apiv1alpha1.Resource("subnetpool"))} +} + +// SubnetPools returns an object that can list and get SubnetPools. +func (s *subnetPoolLister) SubnetPools(namespace string) SubnetPoolNamespaceLister { + return subnetPoolNamespaceLister{listers.NewNamespaced[*apiv1alpha1.SubnetPool](s.ResourceIndexer, namespace)} +} + +// SubnetPoolNamespaceLister helps list and get SubnetPools. +// All objects returned here must be treated as read-only. +type SubnetPoolNamespaceLister interface { + // List lists all SubnetPools in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.SubnetPool, err error) + // Get retrieves the SubnetPool from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.SubnetPool, error) + SubnetPoolNamespaceListerExpansion +} + +// subnetPoolNamespaceLister implements the SubnetPoolNamespaceLister +// interface. +type subnetPoolNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.SubnetPool] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index dc0f9b9cb..cb3862e92 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -32,6 +32,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Service](#service) - [ShareNetwork](#sharenetwork) - [Subnet](#subnet) +- [SubnetPool](#subnetpool) - [Trunk](#trunk) - [User](#user) - [Volume](#volume) @@ -527,6 +528,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [ShareNetworkSpec](#sharenetworkspec) +- [SubnetPoolSpec](#subnetpoolspec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) @@ -2304,6 +2306,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [ShareNetworkSpec](#sharenetworkspec) +- [SubnetPoolSpec](#subnetpoolspec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) @@ -2345,6 +2348,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [ShareNetworkSpec](#sharenetworkspec) +- [SubnetPoolSpec](#subnetpoolspec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) @@ -4728,12 +4732,141 @@ _Appears in:_ | `filter` _[SubnetFilter](#subnetfilter)_ | 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
Optional: \{\}
| +#### SubnetPool +SubnetPool is the Schema for an ORC resource. + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `SubnetPool` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[SubnetPoolSpec](#subnetpoolspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[SubnetPoolStatus](#subnetpoolstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### SubnetPoolFilter + + + +SubnetPoolFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [SubnetPoolImport](#subnetpoolimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `addressScopeRef` _[KubernetesNameRef](#kubernetesnameref)_ | addressScopeRef is a reference to the ORC AddressScope which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### SubnetPoolImport + + + +SubnetPoolImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [SubnetPoolSpec](#subnetpoolspec) + +| 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
MaxLength: 36
Optional: \{\}
| +| `filter` _[SubnetPoolFilter](#subnetpoolfilter)_ | 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
Optional: \{\}
| + + +#### SubnetPoolResourceSpec + + + +SubnetPoolResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [SubnetPoolSpec](#subnetpoolspec) + +| 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: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `addressScopeRef` _[KubernetesNameRef](#kubernetesnameref)_ | addressScopeRef is a reference to the ORC AddressScope which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### SubnetPoolResourceStatus + + + +SubnetPoolResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [SubnetPoolStatus](#subnetpoolstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `addressScopeID` _string_ | addressScopeID is the ID of the AddressScope to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| + + +#### SubnetPoolSpec + + + +SubnetPoolSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [SubnetPool](#subnetpool) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[SubnetPoolImport](#subnetpoolimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[SubnetPoolResourceSpec](#subnetpoolresourcespec)_ | 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: \{\}
| +| `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]
Optional: \{\}
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| + + +#### SubnetPoolStatus + + + +SubnetPoolStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [SubnetPool](#subnetpool) + +| 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
Optional: \{\}
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| +| `resource` _[SubnetPoolResourceStatus](#subnetpoolresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| + + #### SubnetResourceSpec From c38fba914ee9e637347b82f2d5f3ea2a70a6736d Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Fri, 3 Jul 2026 13:15:45 -0300 Subject: [PATCH 3/4] subnetpool: implement controller --- README.md | 1 + api/v1alpha1/subnetpool_types.go | 153 ++++++++- api/v1alpha1/zz_generated.deepcopy.go | 41 ++- cmd/models-schema/zz_generated.openapi.go | 295 ++++++++++++++++++ cmd/resource-generator/main.go | 5 +- .../openstack.k-orc.cloud_subnetpools.yaml | 226 ++++++++++++++ .../openstack_v1alpha1_subnetpool.yaml | 7 +- internal/controllers/subnetpool/actuator.go | 130 ++++++-- .../controllers/subnetpool/actuator_test.go | 151 ++++++++- internal/controllers/subnetpool/status.go | 29 +- .../subnetpool-create-full/00-assert.yaml | 30 +- .../00-create-resource.yaml | 40 ++- .../subnetpool-create-minimal/00-assert.yaml | 8 +- .../00-create-resource.yaml | 9 +- .../00-create-resources-missing-deps.yaml | 23 +- .../01-create-dependencies.yaml | 9 +- .../00-import-resource.yaml | 6 +- .../01-create-trap-resource.yaml | 17 +- .../02-create-resource.yaml | 19 +- .../00-create-resources.yaml | 12 +- .../subnetpool-import/00-import-resource.yaml | 7 +- .../tests/subnetpool-import/01-assert.yaml | 9 +- .../01-create-trap-resource.yaml | 14 +- .../tests/subnetpool-import/02-assert.yaml | 9 +- .../subnetpool-import/02-create-resource.yaml | 8 +- .../tests/subnetpool-import/README.md | 2 +- .../tests/subnetpool-update/00-assert.yaml | 7 +- .../00-minimal-resource.yaml | 9 +- .../tests/subnetpool-update/01-assert.yaml | 6 +- .../01-updated-resource.yaml | 8 +- .../tests/subnetpool-update/02-assert.yaml | 7 +- .../02-reverted-resource.yaml | 4 +- .../02-reverted-subnetpool.yaml | 19 ++ .../api/v1alpha1/subnetpoolfilter.go | 114 ++++++- .../api/v1alpha1/subnetpoolresourcespec.go | 64 +++- .../api/v1alpha1/subnetpoolresourcestatus.go | 122 +++++++- .../applyconfiguration/internal/internal.go | 108 +++++++ website/docs/crd-reference.md | 37 ++- 38 files changed, 1618 insertions(+), 147 deletions(-) create mode 100644 internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-subnetpool.yaml diff --git a/README.md b/README.md index a5663e5fd..284281a5e 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ kubectl delete -f $ORC_RELEASE | service | | ✔ | ✔ | | share network | | | ◐ | | subnet | | ◐ | ◐ | +| subnetpool | | ✔ | ✔ | | trunk | | ✔ | ✔ | | user | | ◐ | ◐ | | volume | | ◐ | ◐ | diff --git a/api/v1alpha1/subnetpool_types.go b/api/v1alpha1/subnetpool_types.go index f7f9a93e8..d663d5c3f 100644 --- a/api/v1alpha1/subnetpool_types.go +++ b/api/v1alpha1/subnetpool_types.go @@ -39,13 +39,55 @@ type SubnetPoolResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="addressScopeRef is immutable" AddressScopeRef *KubernetesNameRef `json:"addressScopeRef,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/networking/v2/extensions/subnetpools - // - // 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"` + // defaultQuota is a per-project quota on the prefix space that + // can be allocated from the SubnetPool for project subnets. Its + // value represents the number of absolute addresses any given + // project is allowed to consume from the pool. + // +optional + // TODO(winiciusallan): Add this field when fixing bug in gophercloud + // DefaultQuota int32 `json:"defaultQuota,omitempty"` + + // prefixes is the list of subnet prefixes to assign to the subnet + // pool. The API merges adjacent prefixes and treats them as a + // single prefix. Each subnet prefix must be unique across all + // subnet pools associated with address scope. + // +kubebuilder:validation:MinItems:=1 + // +kubebuilder:validation:MaxItems:=64 + // +listType=set + // +required + Prefixes []CIDR `json:"prefixes,omitempty"` + + // minPrefixLength is the smallest prefix that can be allocated + // from a subnet pool. For IPv4 subnet pools, default is 8. For + // IPv6 subnet pools, default is 64. + // +kubebuilder:validation:Minimum=1 + // +required + MinPrefixLength int32 `json:"minPrefixLength,omitempty"` + + // maxPrefixLength is the maximum prefix size that can be allocated + // from the subnet pool. For IPv4 subnet pools, default is 32. For + // IPv6 subnet pools, default is 128. + // +kubebuilder:validation:Minimum=1 + // +required + MaxPrefixLength int32 `json:"maxPrefixLength,omitempty"` + + // shared indicates whether this resource is shared across all projects. + // By default, it is false, and only administrative users can + // change this value. + // +optional + Shared *bool `json:"shared,omitempty"` + + // defaultPrefixLength is the size of the prefix to allocate when + // the cidr or prefixlen attributes are omitted when you create + // the subnet. Default is MinPrefixLength. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="defaultPrefixLength is immutable" + // +optional + DefaultPrefixLength int32 `json:"defaultPrefixLength,omitempty"` + + // isDefault defines whether the subnetpool is default pool or + // not. + // +optional + IsDefault *bool `json:"isDefault,omitempty"` } // SubnetPoolFilter defines an existing resource by its properties @@ -56,10 +98,8 @@ type SubnetPoolFilter struct { Name *OpenStackName `json:"name,omitempty"` // description of the existing resource - // +kubebuilder:validation:MinLength:=1 - // +kubebuilder:validation:MaxLength:=255 // +optional - Description *string `json:"description,omitempty"` + Description *NeutronDescription `json:"description,omitempty"` // projectRef is a reference to the ORC Project which this resource is associated with. // +optional @@ -69,9 +109,44 @@ type SubnetPoolFilter struct { // +optional AddressScopeRef *KubernetesNameRef `json:"addressScopeRef,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/networking/v2/extensions/subnetpools + // minPrefixLength allows filtering the subnet pool list result by + // the smallest prefix that can be allocated from a subnet pool. + // +optional + MinPrefixLength int32 `json:"minPrefixLength,omitempty"` + + // maxPrefixLength allows filtering the subnet pool list result by + // the maximum prefix size that can be allocated from the subnet + // pool. + // +optional + MaxPrefixLength int32 `json:"maxPrefixLength,omitempty"` + + // ipVersion is the IP protocol version. It can be either 4 or 6 + // +optional + IPVersion IPVersion `json:"ipVersion,omitempty"` + + // shared allows filtering the list result based on whether the + // resource is shared across all projects. This field is + // admin-only. + // +optional + Shared *bool `json:"shared,omitempty"` + + // defaultPrefixLength allows filtering the subnet pool list + // result by the size of the prefix to allocate when the cidr or + // prefixlen attributes are omitted when you create the subnet. + // +optional + DefaultPrefixLength int32 `json:"defaultPrefixLength,omitempty"` + + // isDefault allows filtering the subnet pool list result based on + // if it is a default pool or not. + // +optional + IsDefault *bool `json:"isDefault,omitempty"` + + // revisionNumber allows filtering the list result by the revision + // number of the resource. + // +optional + RevisionNumber int64 `json:"revisionNumber,omitempty"` + + FilterByNeutronTags `json:",inline"` } // SubnetPoolResourceStatus represents the observed state of the resource. @@ -96,7 +171,53 @@ type SubnetPoolResourceStatus struct { // +optional AddressScopeID string `json:"addressScopeID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the SubnetPool structure from - // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools + // prefixes is a list of prefixes to assign to the SubnetPool. + // +listType=atomic + // +kubebuilder:validation:MaxItems:=1024 + // +kubebuilder:validation:items:MaxLength:=64 + // +optional + Prefixes []string `json:"prefixes,omitempty"` + + // defaultQuota is a per-project quota on the prefix space that + // can be allocated from the SubnetPool for project subnets. + // +optional + DefaultQuota int32 `json:"defaultQuota,omitempty"` + + // minPrefixLength is the smallest prefix that can be allocated + // from a subnet pool. + // +optional + MinPrefixLength int32 `json:"minPrefixLength,omitempty"` + + // maxPrefixLength is the maximum prefix size that can be + // allocated from the subnet pool. + // +optional + MaxPrefixLength int32 `json:"maxPrefixLength,omitempty"` + + // defaultPrefixLength is the size of the prefix to allocate when + // the cidr or prefixlen attributes are omitted when you create + // the subnet. + // +optional + DefaultPrefixLength int32 `json:"defaultPrefixLength,omitempty"` + + // isDefault indicates whether the SubnetPool is the default pool + // when creating subnets. + // +optional + IsDefault bool `json:"isDefault,omitempty"` + + // shared indicates whether the SubnetPool is shared across all projects. + // +optional + Shared bool `json:"shared,omitempty"` + + // ipVersion is the IP protocol version. It can be either 4 or 6 + // +optional + IPVersion int32 `json:"ipVersion,omitempty"` + + // tags is the list of tags on the resource. + // +kubebuilder:validation:MaxItems=64 + // +kubebuilder:validation:items:MaxLength=1024 + // +listType=atomic + // +optional + Tags []string `json:"tags,omitempty"` + + NeutronStatusMetadata `json:",inline"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 12c626fe1..b584afeba 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6492,7 +6492,7 @@ func (in *SubnetPoolFilter) DeepCopyInto(out *SubnetPoolFilter) { } if in.Description != nil { in, out := &in.Description, &out.Description - *out = new(string) + *out = new(NeutronDescription) **out = **in } if in.ProjectRef != nil { @@ -6505,6 +6505,17 @@ func (in *SubnetPoolFilter) DeepCopyInto(out *SubnetPoolFilter) { *out = new(KubernetesNameRef) **out = **in } + if in.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } + if in.IsDefault != nil { + in, out := &in.IsDefault, &out.IsDefault + *out = new(bool) + **out = **in + } + in.FilterByNeutronTags.DeepCopyInto(&out.FilterByNeutronTags) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolFilter. @@ -6597,6 +6608,21 @@ func (in *SubnetPoolResourceSpec) DeepCopyInto(out *SubnetPoolResourceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.Prefixes != nil { + in, out := &in.Prefixes, &out.Prefixes + *out = make([]CIDR, len(*in)) + copy(*out, *in) + } + if in.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } + if in.IsDefault != nil { + in, out := &in.IsDefault, &out.IsDefault + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolResourceSpec. @@ -6612,6 +6638,17 @@ func (in *SubnetPoolResourceSpec) DeepCopy() *SubnetPoolResourceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubnetPoolResourceStatus) DeepCopyInto(out *SubnetPoolResourceStatus) { *out = *in + if in.Prefixes != nil { + in, out := &in.Prefixes, &out.Prefixes + *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) + } + in.NeutronStatusMetadata.DeepCopyInto(&out.NeutronStatusMetadata) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolResourceStatus. @@ -6673,7 +6710,7 @@ func (in *SubnetPoolStatus) DeepCopyInto(out *SubnetPoolStatus) { if in.Resource != nil { in, out := &in.Resource, &out.Resource *out = new(SubnetPoolResourceStatus) - **out = **in + (*in).DeepCopyInto(*out) } } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 046e87c17..2bb123650 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -12105,6 +12105,135 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolFilter(ref c Format: "", }, }, + "minPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "minPrefixLength allows filtering the subnet pool list result by the smallest prefix that can be allocated from a subnet pool.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "maxPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "maxPrefixLength allows filtering the subnet pool list result by the maximum prefix size that can be allocated from the subnet pool.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "ipVersion": { + SchemaProps: spec.SchemaProps{ + Description: "ipVersion is the IP protocol version. It can be either 4 or 6", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "shared": { + SchemaProps: spec.SchemaProps{ + Description: "shared allows filtering the list result based on whether the resource is shared across all projects. This field is admin-only.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "defaultPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "defaultPrefixLength allows filtering the subnet pool list result by the size of the prefix to allocate when the cidr or prefixlen attributes are omitted when you create the subnet.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "isDefault": { + SchemaProps: spec.SchemaProps{ + Description: "isDefault allows filtering the subnet pool list result based on if it is a default pool or not.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "revisionNumber": { + SchemaProps: spec.SchemaProps{ + Description: "revisionNumber allows filtering the list result by the revision number of the resource.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "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: "", + }, + }, + }, + }, + }, }, }, }, @@ -12225,7 +12354,63 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceSpec Format: "", }, }, + "prefixes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "prefixes is the list of subnet prefixes to assign to the subnet pool. The API merges adjacent prefixes and treats them as a single prefix. Each subnet prefix must be unique across all subnet pools associated with address scope.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "minPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "minPrefixLength is the smallest prefix that can be allocated from a subnet pool. For IPv4 subnet pools, default is 8. For IPv6 subnet pools, default is 64.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "maxPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "maxPrefixLength is the maximum prefix size that can be allocated from the subnet pool. For IPv4 subnet pools, default is 32. For IPv6 subnet pools, default is 128.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "shared": { + SchemaProps: spec.SchemaProps{ + Description: "shared indicates whether this resource is shared across all projects. By default, it is false, and only administrative users can change this value.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "defaultPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "defaultPrefixLength is the size of the prefix to allocate when the cidr or prefixlen attributes are omitted when you create the subnet. Default is MinPrefixLength.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "isDefault": { + SchemaProps: spec.SchemaProps{ + Description: "isDefault defines whether the subnetpool is default pool or not.", + Type: []string{"boolean"}, + Format: "", + }, + }, }, + Required: []string{"prefixes", "minPrefixLength", "maxPrefixLength"}, }, }, } @@ -12266,9 +12451,119 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolResourceStat Format: "", }, }, + "prefixes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "prefixes is a list of prefixes to assign to the SubnetPool.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "defaultQuota": { + SchemaProps: spec.SchemaProps{ + Description: "defaultQuota is a per-project quota on the prefix space that can be allocated from the SubnetPool for project subnets.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "minPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "minPrefixLength is the smallest prefix that can be allocated from a subnet pool.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "maxPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "maxPrefixLength is the maximum prefix size that can be allocated from the subnet pool.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "defaultPrefixLength": { + SchemaProps: spec.SchemaProps{ + Description: "defaultPrefixLength is the size of the prefix to allocate when the cidr or prefixlen attributes are omitted when you create the subnet.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "isDefault": { + SchemaProps: spec.SchemaProps{ + Description: "isDefault indicates whether the SubnetPool is the default pool when creating subnets.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "shared": { + SchemaProps: spec.SchemaProps{ + Description: "shared indicates whether the SubnetPool is shared across all projects.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "ipVersion": { + SchemaProps: spec.SchemaProps{ + Description: "ipVersion is the IP protocol version. It can be either 4 or 6", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "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: "", + }, + }, + }, + }, + }, + "createdAt": { + SchemaProps: spec.SchemaProps{ + Description: "createdAt shows the date and time when the resource was created. The date and time stamp format is ISO 8601", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "updatedAt": { + SchemaProps: spec.SchemaProps{ + Description: "updatedAt shows the date and time when the resource was updated. The date and time stamp format is ISO 8601", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "revisionNumber": { + SchemaProps: spec.SchemaProps{ + Description: "revisionNumber optionally set via extensions/standard-attr-revisions", + Type: []string{"integer"}, + Format: "int64", + }, + }, }, }, }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 4fcb369b7..1042cea72 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -210,8 +210,7 @@ func main() { kuttlTestTemplate := template.Must(template.New("kuttl-test").Parse(kuttl_test_template)) crdKustomizationTemplate := template.Must(template.New("crd-kustomization").Parse(crd_kustomization_template)) samplesKustomizationTemplate := template.Must( - template.New("samples-kustomization").Parse(samples_kustomization_template), - ) + template.New("samples-kustomization").Parse(samples_kustomization_template)) mockDocTemplate := template.Must(template.New("mock-doc").Parse(mock_doc_template)) addDefaults(resources) @@ -227,7 +226,7 @@ func main() { controllerDirPath := filepath.Join("internal", "controllers", resource.NameLower) if _, err := os.Stat(controllerDirPath); os.IsNotExist(err) { - err = os.Mkdir(controllerDirPath, 0o755) + err = os.Mkdir(controllerDirPath, 0755) if err != nil { panic(err) } diff --git a/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml b/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml index 8285f441d..6bf448104 100644 --- a/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml @@ -97,23 +97,125 @@ spec: maxLength: 253 minLength: 1 type: string + defaultPrefixLength: + description: |- + defaultPrefixLength allows filtering the subnet pool list + result by the size of the prefix to allocate when the cidr or + prefixlen attributes are omitted when you create the subnet. + format: int32 + type: integer description: description: description of the existing resource maxLength: 255 minLength: 1 type: string + ipVersion: + description: ipVersion is the IP protocol version. It can + be either 4 or 6 + enum: + - 4 + - 6 + format: int32 + type: integer + isDefault: + description: |- + isDefault allows filtering the subnet pool list result based on + if it is a default pool or not. + type: boolean + maxPrefixLength: + description: |- + maxPrefixLength allows filtering the subnet pool list result by + the maximum prefix size that can be allocated from the subnet + pool. + format: int32 + type: integer + minPrefixLength: + description: |- + minPrefixLength allows filtering the subnet pool list result by + the smallest prefix that can be allocated from a subnet pool. + format: int32 + type: integer 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: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + 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: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + 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 which this resource is associated with. maxLength: 253 minLength: 1 type: string + revisionNumber: + description: |- + revisionNumber allows filtering the list result by the revision + number of the resource. + format: int64 + type: integer + shared: + description: |- + shared allows filtering the list result based on whether the + resource is shared across all projects. This field is + admin-only. + type: boolean + 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: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + 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: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + maxLength: 255 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set type: object id: description: |- @@ -171,12 +273,43 @@ spec: x-kubernetes-validations: - message: addressScopeRef is immutable rule: self == oldSelf + defaultPrefixLength: + description: |- + defaultPrefixLength is the size of the prefix to allocate when + the cidr or prefixlen attributes are omitted when you create + the subnet. Default is MinPrefixLength. + format: int32 + type: integer + x-kubernetes-validations: + - message: defaultPrefixLength is immutable + rule: self == oldSelf description: description: description is a human-readable description for the resource. maxLength: 255 minLength: 1 type: string + isDefault: + description: |- + isDefault defines whether the subnetpool is default pool or + not. + type: boolean + maxPrefixLength: + description: |- + maxPrefixLength is the maximum prefix size that can be allocated + from the subnet pool. For IPv4 subnet pools, default is 32. For + IPv6 subnet pools, default is 128. + format: int32 + minimum: 1 + type: integer + minPrefixLength: + description: |- + minPrefixLength is the smallest prefix that can be allocated + from a subnet pool. For IPv4 subnet pools, default is 8. For + IPv6 subnet pools, default is 64. + format: int32 + minimum: 1 + type: integer name: description: |- name will be the name of the created resource. If not specified, the @@ -185,6 +318,21 @@ spec: minLength: 1 pattern: ^[^,]+$ type: string + prefixes: + description: |- + prefixes is the list of subnet prefixes to assign to the subnet + pool. The API merges adjacent prefixes and treats them as a + single prefix. Each subnet prefix must be unique across all + subnet pools associated with address scope. + items: + format: cidr + maxLength: 49 + minLength: 1 + type: string + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-type: set projectRef: description: projectRef is a reference to the ORC Project which this resource is associated with. @@ -194,6 +342,15 @@ spec: x-kubernetes-validations: - message: projectRef is immutable rule: self == oldSelf + shared: + description: |- + shared indicates whether this resource is shared across all projects. + By default, it is false, and only administrative users can + change this value. + type: boolean + required: + - maxPrefixLength + - minPrefixLength type: object required: - cloudCredentialsRef @@ -303,21 +460,90 @@ spec: the resource is associated. maxLength: 1024 type: string + createdAt: + description: createdAt shows the date and time when the resource + was created. The date and time stamp format is ISO 8601 + format: date-time + type: string + defaultPrefixLength: + description: |- + defaultPrefixLength is the size of the prefix to allocate when + the cidr or prefixlen attributes are omitted when you create + the subnet. + format: int32 + type: integer + defaultQuota: + description: |- + defaultQuota is a per-project quota on the prefix space that + can be allocated from the SubnetPool for project subnets. + format: int32 + type: integer description: description: description is a human-readable description for the resource. maxLength: 1024 type: string + ipVersion: + description: ipVersion is the IP protocol version. It can be either + 4 or 6 + format: int32 + type: integer + isDefault: + description: |- + isDefault indicates whether the SubnetPool is the default pool + when creating subnets. + type: boolean + maxPrefixLength: + description: |- + maxPrefixLength is the maximum prefix size that can be + allocated from the subnet pool. + format: int32 + type: integer + minPrefixLength: + description: |- + minPrefixLength is the smallest prefix that can be allocated + from a subnet pool. + format: int32 + type: integer name: description: name is a Human-readable name for the resource. Might not be unique. maxLength: 1024 type: string + prefixes: + description: prefixes is a list of prefixes to assign to the SubnetPool. + items: + maxLength: 64 + type: string + maxItems: 1024 + type: array + x-kubernetes-list-type: atomic projectID: description: projectID is the ID of the Project to which the resource is associated. maxLength: 1024 type: string + revisionNumber: + description: revisionNumber optionally set via extensions/standard-attr-revisions + format: int64 + type: integer + shared: + description: shared indicates whether the SubnetPool is shared + across all projects. + type: boolean + tags: + description: tags is the list of tags on the resource. + items: + maxLength: 1024 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + updatedAt: + description: updatedAt shows the date and time when the resource + was updated. The date and time stamp format is ISO 8601 + format: date-time + type: string type: object type: object required: diff --git a/config/samples/openstack_v1alpha1_subnetpool.yaml b/config/samples/openstack_v1alpha1_subnetpool.yaml index 6ea0df075..c5facc980 100644 --- a/config/samples/openstack_v1alpha1_subnetpool.yaml +++ b/config/samples/openstack_v1alpha1_subnetpool.yaml @@ -5,10 +5,13 @@ metadata: name: subnetpool-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 SubnetPool - # TODO(scaffolding): Add all fields the resource supports + prefixes: + - 10.0.0.0/24 + minPrefixLength: 8 + minPrefixLength: 32 + defaultPrefixLen: 24 diff --git a/internal/controllers/subnetpool/actuator.go b/internal/controllers/subnetpool/actuator.go index 13c530e50..806c4d7bf 100644 --- a/internal/controllers/subnetpool/actuator.go +++ b/internal/controllers/subnetpool/actuator.go @@ -19,6 +19,7 @@ package subnetpool import ( "context" "iter" + "slices" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/subnetpools" corev1 "k8s.io/api/core/v1" @@ -33,6 +34,7 @@ import ( "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" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/tags" ) // OpenStack resource types @@ -50,8 +52,10 @@ type subnetpoolActuator struct { k8sClient client.Client } -var _ createResourceActuator = subnetpoolActuator{} -var _ deleteResourceActuator = subnetpoolActuator{} +var ( + _ createResourceActuator = subnetpoolActuator{} + _ deleteResourceActuator = subnetpoolActuator{} +) func (subnetpoolActuator) GetResourceID(osResource *osResourceT) string { return osResource.ID @@ -71,22 +75,29 @@ func (actuator subnetpoolActuator) ListOSResourcesForAdoption(ctx context.Contex 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 + var projectID string + if resourceSpec.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } listOpts := subnetpools.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Name: getResourceName(orcObject), + ProjectID: projectID, } return actuator.osClient.ListSubnetPools(ctx, listOpts), true } func (actuator subnetpoolActuator) 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 project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( @@ -108,11 +119,21 @@ func (actuator subnetpoolActuator) ListOSResourcesForImport(ctx context.Context, } listOpts := subnetpools.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - ProjectID: ptr.Deref(project.Status.ID, ""), - AddressScopeID: ptr.Deref(addressScope.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + ProjectID: ptr.Deref(project.Status.ID, ""), + AddressScopeID: ptr.Deref(addressScope.Status.ID, ""), + MinPrefixLen: int(filter.MinPrefixLength), + MaxPrefixLen: int(filter.MaxPrefixLength), + IPVersion: int(filter.IPVersion), + Shared: filter.Shared, + DefaultPrefixLen: int(filter.DefaultPrefixLength), + IsDefault: filter.IsDefault, + RevisionNumber: int(filter.RevisionNumber), + Tags: tags.Join(filter.Tags), + TagsAny: tags.Join(filter.TagsAny), + NotTags: tags.Join(filter.NotTags), + NotTagsAny: tags.Join(filter.NotTagsAny), } return actuator.osClient.ListSubnetPools(ctx, listOpts), reconcileStatus @@ -124,7 +145,8 @@ func (actuator subnetpoolActuator) CreateResource(ctx context.Context, obj orcOb 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")) + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"), + ) } var reconcileStatus progress.ReconcileStatus @@ -152,12 +174,23 @@ func (actuator subnetpoolActuator) CreateResource(ctx context.Context, obj orcOb if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } + + prefixes := make([]string, len(resource.Prefixes)) + for i, prefix := range resource.Prefixes { + prefixes[i] = string(prefix) + } + createOpts := subnetpools.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - ProjectID: projectID, - AddressScopeID: addressScopeID, - // TODO(scaffolding): Add more fields + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + ProjectID: projectID, + AddressScopeID: addressScopeID, + Prefixes: prefixes, + MinPrefixLen: int(resource.MinPrefixLength), + MaxPrefixLen: int(resource.MaxPrefixLength), + Shared: ptr.Deref(resource.Shared, false), + DefaultPrefixLen: int(resource.DefaultPrefixLength), + IsDefault: ptr.Deref(resource.IsDefault, false), } osResource, err := actuator.osClient.CreateSubnetPool(ctx, createOpts) @@ -181,20 +214,24 @@ func (actuator subnetpoolActuator) updateResource(ctx context.Context, obj orcOb 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")) + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"), + ) } updateOpts := subnetpools.UpdateOpts{} handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handlePrefixesUpdate(&updateOpts, resource, osResource) + handleMinPrefixLengthUpdate(&updateOpts, resource, osResource) + handleMaxPrefixLengthUpdate(&updateOpts, resource, osResource) + handleIsDefaultUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { return progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)) + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err), + ) } if !needsUpdate { log.V(logging.Debug).Info("No changes") @@ -202,7 +239,6 @@ func (actuator subnetpoolActuator) updateResource(ctx context.Context, obj orcOb } _, err = actuator.osClient.UpdateSubnetPool(ctx, osResource.ID, updateOpts) - if err != nil { if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) @@ -219,7 +255,7 @@ func needsUpdate(updateOpts subnetpools.UpdateOpts) (bool, error) { return false, err } - updateMap, ok := updateOptsMap["subnet_pool"].(map[string]any) + updateMap, ok := updateOptsMap["subnetpool"].(map[string]any) if !ok { updateMap = make(map[string]any) } @@ -230,7 +266,7 @@ func needsUpdate(updateOpts subnetpools.UpdateOpts) (bool, error) { func handleNameUpdate(updateOpts *subnetpools.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { name := getResourceName(obj) if osResource.Name != name { - updateOpts.Name = &name + updateOpts.Name = name } } @@ -241,6 +277,44 @@ func handleDescriptionUpdate(updateOpts *subnetpools.UpdateOpts, resource *resou } } +func handleMinPrefixLengthUpdate(updateOpts *subnetpools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + minPrefixLen := resource.MinPrefixLength + if minPrefixLen != int32(osResource.MinPrefixLen) { + updateOpts.MinPrefixLen = int(minPrefixLen) + } +} + +func handleMaxPrefixLengthUpdate(updateOpts *subnetpools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + maxPrefixLength := resource.MaxPrefixLength + if maxPrefixLength != int32(osResource.MaxPrefixLen) { + updateOpts.MaxPrefixLen = int(maxPrefixLength) + } +} + +func handlePrefixesUpdate(updateOpts *subnetpools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + desiredPrefixes := make([]string, len(resource.Prefixes)) + for i := range resource.Prefixes { + desiredPrefixes[i] = string(resource.Prefixes[i]) + } + slices.Sort(desiredPrefixes) + + currentPrefixes := make([]string, len(osResource.Prefixes)) + copy(currentPrefixes, osResource.Prefixes) + slices.Sort(currentPrefixes) + + if !slices.Equal(desiredPrefixes, currentPrefixes) { + updateOpts.Prefixes = desiredPrefixes + } +} + +func handleIsDefaultUpdate(updateOpts *subnetpools.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + // fallback to the default value if unset. + isDefault := ptr.Deref(resource.IsDefault, false) + if isDefault != osResource.IsDefault { + updateOpts.IsDefault = &isDefault + } +} + func (actuator subnetpoolActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ actuator.updateResource, diff --git a/internal/controllers/subnetpool/actuator_test.go b/internal/controllers/subnetpool/actuator_test.go index ef7cfcce0..c600e893d 100644 --- a/internal/controllers/subnetpool/actuator_test.go +++ b/internal/controllers/subnetpool/actuator_test.go @@ -37,7 +37,7 @@ func TestNeedsUpdate(t *testing.T) { }, { name: "Updated opts", - updateOpts: subnetpools.UpdateOpts{Name: ptr.To("updated")}, + updateOpts: subnetpools.UpdateOpts{Name: "updated"}, expectChange: true, }, } @@ -83,7 +83,6 @@ func TestHandleNameUpdate(t *testing.T) { t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) } }) - } } @@ -114,6 +113,154 @@ func TestHandleDescriptionUpdate(t *testing.T) { t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) } }) + } +} + +func TestHandleMinPrefixLengthUpdate(t *testing.T) { + testCases := []struct { + name string + newValue int32 + existingValue int + expectChange bool + }{ + {name: "Identical", newValue: 8, existingValue: 8, expectChange: false}, + {name: "Different", newValue: 16, existingValue: 8, expectChange: true}, + {name: "Increased", newValue: 24, existingValue: 16, expectChange: true}, + {name: "Decreased", newValue: 8, existingValue: 16, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.SubnetPoolResourceSpec{MinPrefixLength: tt.newValue} + osResource := &osResourceT{MinPrefixLen: tt.existingValue} + + updateOpts := subnetpools.UpdateOpts{} + handleMinPrefixLengthUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleMaxPrefixLengthUpdate(t *testing.T) { + testCases := []struct { + name string + newValue int32 + existingValue int + expectChange bool + }{ + {name: "Identical", newValue: 32, existingValue: 32, expectChange: false}, + {name: "Different", newValue: 28, existingValue: 32, expectChange: true}, + {name: "Increased", newValue: 128, existingValue: 32, expectChange: true}, + {name: "Decreased", newValue: 24, existingValue: 32, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.SubnetPoolResourceSpec{MaxPrefixLength: tt.newValue} + osResource := &osResourceT{MaxPrefixLen: tt.existingValue} + + updateOpts := subnetpools.UpdateOpts{} + handleMaxPrefixLengthUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandlePrefixesUpdate(t *testing.T) { + testCases := []struct { + name string + newPrefixes []orcv1alpha1.CIDR + existingPrefixes []string + expectChange bool + }{ + { + name: "Identical prefixes", + newPrefixes: []orcv1alpha1.CIDR{"192.168.0.0/24", "10.0.0.0/16"}, + existingPrefixes: []string{"192.168.0.0/24", "10.0.0.0/16"}, + expectChange: false, + }, + { + name: "Different prefixes", + newPrefixes: []orcv1alpha1.CIDR{"192.168.0.0/24"}, + existingPrefixes: []string{"10.0.0.0/16"}, + expectChange: true, + }, + { + name: "Prefixes out of order", + newPrefixes: []orcv1alpha1.CIDR{"10.0.0.0/16", "192.168.0.0/24"}, + existingPrefixes: []string{"192.168.0.0/24", "10.0.0.0/16"}, + expectChange: false, + }, + { + name: "Extra prefix in existing", + newPrefixes: []orcv1alpha1.CIDR{"192.168.0.0/24"}, + existingPrefixes: []string{"192.168.0.0/24", "10.0.0.0/16"}, + expectChange: true, + }, + { + name: "Extra prefix in new", + newPrefixes: []orcv1alpha1.CIDR{"192.168.0.0/24", "10.0.0.0/16"}, + existingPrefixes: []string{"192.168.0.0/24"}, + expectChange: true, + }, + { + name: "Empty prefixes", + newPrefixes: []orcv1alpha1.CIDR{}, + existingPrefixes: []string{}, + expectChange: false, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.SubnetPoolResourceSpec{Prefixes: tt.newPrefixes} + osResource := &osResourceT{Prefixes: tt.existingPrefixes} + + updateOpts := subnetpools.UpdateOpts{} + handlePrefixesUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleIsDefaultUpdate(t *testing.T) { + ptrToBool := ptr.To[bool] + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Identical", newValue: ptrToBool(true), existingValue: true, expectChange: false}, + {name: "Different", newValue: ptrToBool(true), existingValue: false, expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: true, expectChange: true}, + {name: "No value provided, existing is default", newValue: nil, existingValue: false, expectChange: false}, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.SubnetPoolResourceSpec{IsDefault: tt.newValue} + osResource := &osResourceT{IsDefault: tt.existingValue} + + updateOpts := subnetpools.UpdateOpts{} + handleIsDefaultUpdate(&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/subnetpool/status.go b/internal/controllers/subnetpool/status.go index 7cfd6c22a..837c0a1da 100644 --- a/internal/controllers/subnetpool/status.go +++ b/internal/controllers/subnetpool/status.go @@ -51,15 +51,34 @@ func (subnetpoolStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Sub func (subnetpoolStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.SubnetPoolResourceStatus(). WithProjectID(osResource.ProjectID). - WithAddressScopeID(osResource.AddressScopeID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the SubnetPoolResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + WithName(osResource.Name). + WithPrefixes(osResource.Prefixes...). + WithMinPrefixLength(int32(osResource.MinPrefixLen)). + WithMaxPrefixLength(int32(osResource.MaxPrefixLen)). + WithDefaultPrefixLength(int32(osResource.DefaultPrefixLen)). + WithIsDefault(osResource.IsDefault). + WithShared(osResource.Shared). + WithDefaultQuota(int32(osResource.DefaultQuota)). + WithRevisionNumber(int64(osResource.RevisionNumber)). + WithCreatedAt(metav1.NewTime(osResource.CreatedAt)). + WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)). + WithIPVersion(int32(osResource.IPversion)) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } + if osResource.AddressScopeID != "" { + resourceStatus.WithAddressScopeID(osResource.AddressScopeID) + } + + if osResource.ProjectID != "" { + resourceStatus.WithProjectID(osResource.ProjectID) + } + + if len(osResource.Tags) != 0 { + resourceStatus.WithTags(osResource.Tags...) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml index 46a178391..517419331 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-assert.yaml @@ -7,7 +7,28 @@ status: resource: name: subnetpool-create-full-override description: SubnetPool from "create full" test - # TODO(scaffolding): Add all fields the resource supports + prefixes: + - 192.168.11.0/24 + minPrefixLength: 16 + maxPrefixLength: 28 + defaultPrefixLength: 24 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-create-full-admin +status: + resource: + name: subnetpool-create-full-admin + isDefault: false + shared: true conditions: - type: Available status: "True" @@ -23,6 +44,10 @@ resourceRefs: kind: SubnetPool name: subnetpool-create-full ref: subnetpool + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: SubnetPool + name: subnetpool-create-full-admin + ref: subnetpoolAdmin - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: subnetpool-create-full @@ -33,6 +58,5 @@ resourceRefs: ref: addressScope assertAll: - celExpr: "subnetpool.status.id != ''" - - celExpr: "subnetpool.status.resource.projectID == project.status.id" + - celExpr: "subnetpoolAdmin.status.resource.projectID == project.status.id" - celExpr: "subnetpool.status.resource.addressScopeID == addressScope.status.id" - # TODO(scaffolding): Add more checks diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml index 5537dd62a..725eeed7b 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-create-full/00-create-resource.yaml @@ -5,11 +5,9 @@ metadata: name: subnetpool-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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,12 +16,11 @@ metadata: name: subnetpool-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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: + ipVersion: 4 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool @@ -31,13 +28,38 @@ metadata: name: subnetpool-create-full 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: subnetpool-create-full-override description: SubnetPool from "create full" test - projectRef: subnetpool-create-full addressScopeRef: subnetpool-create-full - # TODO(scaffolding): Add all fields the resource supports + prefixes: + - 192.168.11.0/24 + minPrefixLength: 16 + maxPrefixLength: 28 + defaultPrefixLength: 24 + isDefault: false +--- +# Creating a SubnetPool as admin to test `projectRef` and `shared` +# fields. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-create-full-admin +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + projectRef: subnetpool-create-full + prefixes: + - 192.168.12.0/24 + minPrefixLength: 16 + maxPrefixLength: 28 + # Devstack automatically creates a default subnet pool that + # prevent us to create a new default pool. + isDefault: false + shared: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml index 4ba550402..bf95b0a85 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-assert.yaml @@ -6,7 +6,10 @@ metadata: status: resource: name: subnetpool-create-minimal - # TODO(scaffolding): Add all fields the resource supports + prefixes: + - 192.168.10.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 conditions: - type: Available status: "True" @@ -24,4 +27,5 @@ resourceRefs: ref: subnetpool assertAll: - celExpr: "subnetpool.status.id != ''" - # TODO(scaffolding): Add more checks + - celExpr: "!has(subnetpool.status.resource.description)" + - celExpr: "!has(subnetpool.status.resource.addressScopeID)" diff --git a/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml index f509d8edf..381802380 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-create-minimal/00-create-resource.yaml @@ -5,10 +5,11 @@ metadata: name: subnetpool-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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: {} + resource: + prefixes: + - 192.168.10.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml index af4550327..66e197db9 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/00-create-resources-missing-deps.yaml @@ -5,26 +5,31 @@ metadata: name: subnetpool-dependency-no-project spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: projectRef: subnetpool-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- + prefixes: + - 192.168.15.0/24 + minPrefixLength: 16 + maxPrefixLength: 32 +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool metadata: name: subnetpool-dependency-no-addressscope spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: addressScopeRef: subnetpool-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + prefixes: + - 192.168.16.0/24 + minPrefixLength: 16 + maxPrefixLength: 32 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool @@ -32,9 +37,11 @@ metadata: name: subnetpool-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: subnetpool-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + prefixes: + - 192.168.17.0/24 + minPrefixLength: 16 + maxPrefixLength: 32 diff --git a/internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml b/internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml index d39d579fa..1f50fc888 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-dependency/01-create-dependencies.yaml @@ -11,11 +11,9 @@ metadata: name: subnetpool-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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,9 +22,8 @@ metadata: name: subnetpool-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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: + ipVersion: 4 diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml index 71d31b1c5..d1e586130 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/00-import-resource.yaml @@ -5,7 +5,7 @@ metadata: name: subnetpool-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -31,7 +31,9 @@ metadata: name: subnetpool-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + # Using admin credentials so we can pass a different project than + # the authenticated one. + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml index 17f7d5a05..24c4517f7 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/01-create-trap-resource.yaml @@ -5,11 +5,9 @@ metadata: name: subnetpool-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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,12 +16,11 @@ metadata: name: subnetpool-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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: + ipVersion: 4 --- # This `subnetpool-import-dependency-not-this-one` should not be picked by the import filter apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -32,11 +29,13 @@ metadata: name: subnetpool-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: projectRef: subnetpool-import-dependency-not-this-one addressScopeRef: subnetpool-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + prefixes: + - 192.168.12.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml index 29aafe5e6..d0a33f520 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import-dependency/02-create-resource.yaml @@ -5,11 +5,9 @@ metadata: name: subnetpool-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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,12 +16,11 @@ metadata: name: subnetpool-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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: + ipVersion: 4 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool @@ -31,11 +28,15 @@ metadata: name: subnetpool-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + # Using admin credentials so we can pass a different project than + # the authenticated one. + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: projectRef: subnetpool-import-dependency-external addressScopeRef: subnetpool-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + prefixes: + - 192.168.13.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 diff --git a/internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml index f66b14477..19da06f6d 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import-error/00-create-resources.yaml @@ -5,13 +5,15 @@ metadata: name: subnetpool-import-error-external-1 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + minPrefixLength: 8 + maxPrefixLength: 32 + prefixes: + - 192.168.10.0/24 description: SubnetPool from "import error" test - # TODO(scaffolding): add any required field --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool @@ -19,10 +21,12 @@ metadata: name: subnetpool-import-error-external-2 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + minPrefixLength: 8 + maxPrefixLength: 32 + prefixes: + - 192.168.10.0/24 description: SubnetPool from "import error" test - # TODO(scaffolding): add any required field diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml index 589fd6d12..e5e7e56d5 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import/00-import-resource.yaml @@ -12,4 +12,9 @@ spec: filter: name: subnetpool-import-external description: SubnetPool subnetpool-import-external from "subnetpool-import" test - # TODO(scaffolding): Add all fields supported by the filter + minPrefixLength: 16 + maxPrefixLength: 32 + ipVersion: 4 + shared: false + isDefault: false + revisionNumber: 0 diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml index 2fffffeb7..cdd66075e 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import/01-assert.yaml @@ -16,7 +16,14 @@ status: resource: name: subnetpool-import-external-not-this-one description: SubnetPool subnetpool-import-external from "subnetpool-import" test - # TODO(scaffolding): Add fields necessary to match filter + prefixes: + - 10.0.0.0/16 + minPrefixLength: 16 + maxPrefixLength: 28 + ipVersion: 4 + shared: false + isDefault: false + revisionNumber: 0 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml index ded2d524e..3453f0632 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import/01-create-trap-resource.yaml @@ -1,17 +1,23 @@ --- # This `subnetpool-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. +# - ensure that we can successfully create another resource which name is a superstring of it (i.e. it's not being adopted) +# - ensure that importing a resource which name is a superstring of it will not pick this one. apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool metadata: name: subnetpool-import-external-not-this-one 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: SubnetPool subnetpool-import-external from "subnetpool-import" test - # TODO(scaffolding): Add fields necessary to match filter + prefixes: + - 10.0.0.0/16 + minPrefixLength: 16 + maxPrefixLength: 28 + ipVersion: 4 + shared: false + isDefault: false + revisionNumber: 1 diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml index 3945561d8..8ad8bce85 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import/02-assert.yaml @@ -30,4 +30,11 @@ status: resource: name: subnetpool-import-external description: SubnetPool subnetpool-import-external from "subnetpool-import" test - # TODO(scaffolding): Add all fields the resource supports + prefixes: + - 192.168.10.0/24 + minPrefixLength: 16 + maxPrefixLength: 32 + defaultPrefixLength: 16 + shared: false + isDefault: false + revisionNumber: 0 diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml index bccca768e..0733d8f51 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-import/02-create-resource.yaml @@ -5,10 +5,14 @@ metadata: name: subnetpool-import-external 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: SubnetPool subnetpool-import-external from "subnetpool-import" test - # TODO(scaffolding): Add fields necessary to match filter + prefixes: + - 192.168.10.0/24 + minPrefixLength: 16 + maxPrefixLength: 32 + shared: false + isDefault: false diff --git a/internal/controllers/subnetpool/tests/subnetpool-import/README.md b/internal/controllers/subnetpool/tests/subnetpool-import/README.md index bf9ee1f88..e063e5a8f 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-import/README.md +++ b/internal/controllers/subnetpool/tests/subnetpool-import/README.md @@ -2,7 +2,7 @@ ## Step 00 -Import a subnetpool that matches all fields in the filter, and verify it is waiting for the external resource to be created. +Import a subnetpool that matches all fields in the filter (name, description, minPrefixLength), and verify it is waiting for the external resource to be created. ## Step 01 diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml index 066103311..323942e49 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-update/00-assert.yaml @@ -7,7 +7,9 @@ resourceRefs: name: subnetpool-update ref: subnetpool assertAll: + - celExpr: "subnetpool.status.id != ''" - celExpr: "!has(subnetpool.status.resource.description)" + - celExpr: "!has(subnetpool.status.resource.addressScopeID)" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool @@ -16,7 +18,10 @@ metadata: status: resource: name: subnetpool-update - # TODO(scaffolding): Add matches for more fields + prefixes: + - 192.168.10.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 conditions: - type: Available status: "True" diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml index 2ea6ac9e2..a808a54e5 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-update/00-minimal-resource.yaml @@ -5,10 +5,11 @@ metadata: name: subnetpool-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource 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: + prefixes: + - 192.168.10.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml index a1b1434f9..cd1dcd5f1 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-update/01-assert.yaml @@ -7,7 +7,11 @@ status: resource: name: subnetpool-update-updated description: subnetpool-update-updated - # TODO(scaffolding): match all fields that were modified + prefixes: + - 10.0.0.0/16 + - 192.168.10.0/24 + minPrefixLength: 7 + maxPrefixLength: 30 conditions: - type: Available status: "True" diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml index 8a21ba7e4..964cd5518 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-update/01-updated-resource.yaml @@ -7,4 +7,10 @@ spec: resource: name: subnetpool-update-updated description: subnetpool-update-updated - # TODO(scaffolding): update all mutable fields + prefixes: + - 10.0.0.0/16 + - 192.168.10.0/24 + minPrefixLength: 7 + maxPrefixLength: 30 + + diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml index 3742a7541..51d6d0768 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-update/02-assert.yaml @@ -8,6 +8,7 @@ resourceRefs: ref: subnetpool assertAll: - celExpr: "!has(subnetpool.status.resource.description)" + - celExpr: "!has(subnetpool.status.resource.addressScopeID)" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: SubnetPool @@ -16,7 +17,11 @@ metadata: status: resource: name: subnetpool-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + prefixes: + - 10.0.0.0/16 + - 192.168.10.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 conditions: - type: Available status: "True" diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml index 2c6c253ff..f555eaeca 100644 --- a/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml +++ b/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-resource.yaml @@ -1,7 +1,9 @@ # NOTE: kuttl only does patch updates, which means we can't delete a field. # We have to use a kubectl apply command instead. +# NOTE: prefixes must be a superset of the previous value because OpenStack +# does not allow removing existing prefixes from a subnet pool. apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - - command: kubectl replace -f 00-minimal-resource.yaml + - command: kubectl replace -f 02-reverted-subnetpool.yaml namespaced: true diff --git a/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-subnetpool.yaml b/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-subnetpool.yaml new file mode 100644 index 000000000..c1f0226a7 --- /dev/null +++ b/internal/controllers/subnetpool/tests/subnetpool-update/02-reverted-subnetpool.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: SubnetPool +metadata: + name: subnetpool-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + # prefixes must be a superset of the previous value because + # OpenStack does not allow removing existing prefixes from a + # subnet pool. Thus, using a separated file to revert changes. + prefixes: + - 10.0.0.0/16 + - 192.168.10.0/24 + minPrefixLength: 8 + maxPrefixLength: 32 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go index ab1048834..82cb28a46 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolfilter.go @@ -25,10 +25,18 @@ import ( // SubnetPoolFilterApplyConfiguration represents a declarative configuration of the SubnetPoolFilter type for use // with apply. type SubnetPoolFilterApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` - AddressScopeRef *apiv1alpha1.KubernetesNameRef `json:"addressScopeRef,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AddressScopeRef *apiv1alpha1.KubernetesNameRef `json:"addressScopeRef,omitempty"` + MinPrefixLength *int32 `json:"minPrefixLength,omitempty"` + MaxPrefixLength *int32 `json:"maxPrefixLength,omitempty"` + IPVersion *apiv1alpha1.IPVersion `json:"ipVersion,omitempty"` + Shared *bool `json:"shared,omitempty"` + DefaultPrefixLength *int32 `json:"defaultPrefixLength,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` + RevisionNumber *int64 `json:"revisionNumber,omitempty"` + FilterByNeutronTagsApplyConfiguration `json:",inline"` } // SubnetPoolFilterApplyConfiguration constructs a declarative configuration of the SubnetPoolFilter type for use with @@ -48,7 +56,7 @@ func (b *SubnetPoolFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStac // 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 *SubnetPoolFilterApplyConfiguration) WithDescription(value string) *SubnetPoolFilterApplyConfiguration { +func (b *SubnetPoolFilterApplyConfiguration) WithDescription(value apiv1alpha1.NeutronDescription) *SubnetPoolFilterApplyConfiguration { b.Description = &value return b } @@ -68,3 +76,99 @@ func (b *SubnetPoolFilterApplyConfiguration) WithAddressScopeRef(value apiv1alph b.AddressScopeRef = &value return b } + +// WithMinPrefixLength sets the MinPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MinPrefixLength field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithMinPrefixLength(value int32) *SubnetPoolFilterApplyConfiguration { + b.MinPrefixLength = &value + return b +} + +// WithMaxPrefixLength sets the MaxPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxPrefixLength field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithMaxPrefixLength(value int32) *SubnetPoolFilterApplyConfiguration { + b.MaxPrefixLength = &value + return b +} + +// WithIPVersion sets the IPVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IPVersion field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithIPVersion(value apiv1alpha1.IPVersion) *SubnetPoolFilterApplyConfiguration { + b.IPVersion = &value + return b +} + +// WithShared sets the Shared field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Shared field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithShared(value bool) *SubnetPoolFilterApplyConfiguration { + b.Shared = &value + return b +} + +// WithDefaultPrefixLength sets the DefaultPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultPrefixLength field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithDefaultPrefixLength(value int32) *SubnetPoolFilterApplyConfiguration { + b.DefaultPrefixLength = &value + return b +} + +// WithIsDefault sets the IsDefault field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IsDefault field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithIsDefault(value bool) *SubnetPoolFilterApplyConfiguration { + b.IsDefault = &value + return b +} + +// WithRevisionNumber sets the RevisionNumber field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RevisionNumber field is set to the value of the last call. +func (b *SubnetPoolFilterApplyConfiguration) WithRevisionNumber(value int64) *SubnetPoolFilterApplyConfiguration { + b.RevisionNumber = &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 *SubnetPoolFilterApplyConfiguration) WithTags(values ...apiv1alpha1.NeutronTag) *SubnetPoolFilterApplyConfiguration { + for i := range values { + b.FilterByNeutronTagsApplyConfiguration.Tags = append(b.FilterByNeutronTagsApplyConfiguration.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 *SubnetPoolFilterApplyConfiguration) WithTagsAny(values ...apiv1alpha1.NeutronTag) *SubnetPoolFilterApplyConfiguration { + for i := range values { + b.FilterByNeutronTagsApplyConfiguration.TagsAny = append(b.FilterByNeutronTagsApplyConfiguration.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 *SubnetPoolFilterApplyConfiguration) WithNotTags(values ...apiv1alpha1.NeutronTag) *SubnetPoolFilterApplyConfiguration { + for i := range values { + b.FilterByNeutronTagsApplyConfiguration.NotTags = append(b.FilterByNeutronTagsApplyConfiguration.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 *SubnetPoolFilterApplyConfiguration) WithNotTagsAny(values ...apiv1alpha1.NeutronTag) *SubnetPoolFilterApplyConfiguration { + for i := range values { + b.FilterByNeutronTagsApplyConfiguration.NotTagsAny = append(b.FilterByNeutronTagsApplyConfiguration.NotTagsAny, values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go index 5f9bdedc9..cadbdfea6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcespec.go @@ -25,10 +25,16 @@ import ( // SubnetPoolResourceSpecApplyConfiguration represents a declarative configuration of the SubnetPoolResourceSpec type for use // with apply. type SubnetPoolResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` - AddressScopeRef *apiv1alpha1.KubernetesNameRef `json:"addressScopeRef,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AddressScopeRef *apiv1alpha1.KubernetesNameRef `json:"addressScopeRef,omitempty"` + Prefixes []apiv1alpha1.CIDR `json:"prefixes,omitempty"` + MinPrefixLength *int32 `json:"minPrefixLength,omitempty"` + MaxPrefixLength *int32 `json:"maxPrefixLength,omitempty"` + Shared *bool `json:"shared,omitempty"` + DefaultPrefixLength *int32 `json:"defaultPrefixLength,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` } // SubnetPoolResourceSpecApplyConfiguration constructs a declarative configuration of the SubnetPoolResourceSpec type for use with @@ -68,3 +74,53 @@ func (b *SubnetPoolResourceSpecApplyConfiguration) WithAddressScopeRef(value api b.AddressScopeRef = &value return b } + +// WithPrefixes adds the given value to the Prefixes 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 Prefixes field. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithPrefixes(values ...apiv1alpha1.CIDR) *SubnetPoolResourceSpecApplyConfiguration { + for i := range values { + b.Prefixes = append(b.Prefixes, values[i]) + } + return b +} + +// WithMinPrefixLength sets the MinPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MinPrefixLength field is set to the value of the last call. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithMinPrefixLength(value int32) *SubnetPoolResourceSpecApplyConfiguration { + b.MinPrefixLength = &value + return b +} + +// WithMaxPrefixLength sets the MaxPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxPrefixLength field is set to the value of the last call. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithMaxPrefixLength(value int32) *SubnetPoolResourceSpecApplyConfiguration { + b.MaxPrefixLength = &value + return b +} + +// WithShared sets the Shared field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Shared field is set to the value of the last call. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithShared(value bool) *SubnetPoolResourceSpecApplyConfiguration { + b.Shared = &value + return b +} + +// WithDefaultPrefixLength sets the DefaultPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultPrefixLength field is set to the value of the last call. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithDefaultPrefixLength(value int32) *SubnetPoolResourceSpecApplyConfiguration { + b.DefaultPrefixLength = &value + return b +} + +// WithIsDefault sets the IsDefault field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IsDefault field is set to the value of the last call. +func (b *SubnetPoolResourceSpecApplyConfiguration) WithIsDefault(value bool) *SubnetPoolResourceSpecApplyConfiguration { + b.IsDefault = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go index 738c26bee..de6cf0803 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolresourcestatus.go @@ -18,13 +18,27 @@ limitations under the License. package v1alpha1 +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + // SubnetPoolResourceStatusApplyConfiguration represents a declarative configuration of the SubnetPoolResourceStatus type for use // with apply. type SubnetPoolResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - ProjectID *string `json:"projectID,omitempty"` - AddressScopeID *string `json:"addressScopeID,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + AddressScopeID *string `json:"addressScopeID,omitempty"` + Prefixes []string `json:"prefixes,omitempty"` + DefaultQuota *int32 `json:"defaultQuota,omitempty"` + MinPrefixLength *int32 `json:"minPrefixLength,omitempty"` + MaxPrefixLength *int32 `json:"maxPrefixLength,omitempty"` + DefaultPrefixLength *int32 `json:"defaultPrefixLength,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` + Shared *bool `json:"shared,omitempty"` + IPVersion *int32 `json:"ipVersion,omitempty"` + Tags []string `json:"tags,omitempty"` + NeutronStatusMetadataApplyConfiguration `json:",inline"` } // SubnetPoolResourceStatusApplyConfiguration constructs a declarative configuration of the SubnetPoolResourceStatus type for use with @@ -64,3 +78,103 @@ func (b *SubnetPoolResourceStatusApplyConfiguration) WithAddressScopeID(value st b.AddressScopeID = &value return b } + +// WithPrefixes adds the given value to the Prefixes 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 Prefixes field. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithPrefixes(values ...string) *SubnetPoolResourceStatusApplyConfiguration { + for i := range values { + b.Prefixes = append(b.Prefixes, values[i]) + } + return b +} + +// WithDefaultQuota sets the DefaultQuota field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultQuota field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithDefaultQuota(value int32) *SubnetPoolResourceStatusApplyConfiguration { + b.DefaultQuota = &value + return b +} + +// WithMinPrefixLength sets the MinPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MinPrefixLength field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithMinPrefixLength(value int32) *SubnetPoolResourceStatusApplyConfiguration { + b.MinPrefixLength = &value + return b +} + +// WithMaxPrefixLength sets the MaxPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MaxPrefixLength field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithMaxPrefixLength(value int32) *SubnetPoolResourceStatusApplyConfiguration { + b.MaxPrefixLength = &value + return b +} + +// WithDefaultPrefixLength sets the DefaultPrefixLength field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultPrefixLength field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithDefaultPrefixLength(value int32) *SubnetPoolResourceStatusApplyConfiguration { + b.DefaultPrefixLength = &value + return b +} + +// WithIsDefault sets the IsDefault field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IsDefault field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithIsDefault(value bool) *SubnetPoolResourceStatusApplyConfiguration { + b.IsDefault = &value + return b +} + +// WithShared sets the Shared field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Shared field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithShared(value bool) *SubnetPoolResourceStatusApplyConfiguration { + b.Shared = &value + return b +} + +// WithIPVersion sets the IPVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IPVersion field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithIPVersion(value int32) *SubnetPoolResourceStatusApplyConfiguration { + b.IPVersion = &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 *SubnetPoolResourceStatusApplyConfiguration) WithTags(values ...string) *SubnetPoolResourceStatusApplyConfiguration { + for i := range values { + b.Tags = append(b.Tags, values[i]) + } + return b +} + +// WithCreatedAt sets the CreatedAt field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreatedAt field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithCreatedAt(value v1.Time) *SubnetPoolResourceStatusApplyConfiguration { + b.NeutronStatusMetadataApplyConfiguration.CreatedAt = &value + return b +} + +// WithUpdatedAt sets the UpdatedAt field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UpdatedAt field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithUpdatedAt(value v1.Time) *SubnetPoolResourceStatusApplyConfiguration { + b.NeutronStatusMetadataApplyConfiguration.UpdatedAt = &value + return b +} + +// WithRevisionNumber sets the RevisionNumber field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RevisionNumber field is set to the value of the last call. +func (b *SubnetPoolResourceStatusApplyConfiguration) WithRevisionNumber(value int64) *SubnetPoolResourceStatusApplyConfiguration { + b.NeutronStatusMetadataApplyConfiguration.RevisionNumber = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index ba672e633..68272306c 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3666,15 +3666,60 @@ var schemaYAML = typed.YAMLObject(`types: - name: addressScopeRef type: scalar: string + - name: defaultPrefixLength + type: + scalar: numeric - name: description type: scalar: string + - name: ipVersion + type: + scalar: numeric + - name: isDefault + type: + scalar: boolean + - name: maxPrefixLength + type: + scalar: numeric + - name: minPrefixLength + type: + scalar: numeric - 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: revisionNumber + type: + scalar: numeric + - name: shared + type: + scalar: boolean + - 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.SubnetPoolImport map: fields: @@ -3690,30 +3735,93 @@ var schemaYAML = typed.YAMLObject(`types: - name: addressScopeRef type: scalar: string + - name: defaultPrefixLength + type: + scalar: numeric - name: description type: scalar: string + - name: isDefault + type: + scalar: boolean + - name: maxPrefixLength + type: + scalar: numeric + - name: minPrefixLength + type: + scalar: numeric - name: name type: scalar: string + - name: prefixes + type: + list: + elementType: + scalar: string + elementRelationship: associative - name: projectRef type: scalar: string + - name: shared + type: + scalar: boolean - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolResourceStatus map: fields: - name: addressScopeID type: scalar: string + - name: createdAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: defaultPrefixLength + type: + scalar: numeric + - name: defaultQuota + type: + scalar: numeric - name: description type: scalar: string + - name: ipVersion + type: + scalar: numeric + - name: isDefault + type: + scalar: boolean + - name: maxPrefixLength + type: + scalar: numeric + - name: minPrefixLength + type: + scalar: numeric - name: name type: scalar: string + - name: prefixes + type: + list: + elementType: + scalar: string + elementRelationship: atomic - name: projectID type: scalar: string + - name: revisionNumber + type: + scalar: numeric + - name: shared + type: + scalar: boolean + - name: tags + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: updatedAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolSpec map: fields: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index cb3862e92..afe8daa8f 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -493,6 +493,7 @@ _Appears in:_ - [SecurityGroupRule](#securitygrouprule) - [ServerSchedulerHints](#serverschedulerhints) - [SubnetFilter](#subnetfilter) +- [SubnetPoolResourceSpec](#subnetpoolresourcespec) - [SubnetResourceSpec](#subnetresourcespec) @@ -912,6 +913,7 @@ _Appears in:_ - [RouterFilter](#routerfilter) - [SecurityGroupFilter](#securitygroupfilter) - [SubnetFilter](#subnetfilter) +- [SubnetPoolFilter](#subnetpoolfilter) - [TrunkFilter](#trunkfilter) | Field | Description | Default | Validation | @@ -1521,6 +1523,7 @@ _Appears in:_ - [AddressScopeFilter](#addressscopefilter) - [AddressScopeResourceSpec](#addressscoperesourcespec) - [SubnetFilter](#subnetfilter) +- [SubnetPoolFilter](#subnetpoolfilter) - [SubnetResourceSpec](#subnetresourcespec) @@ -2545,6 +2548,7 @@ _Appears in:_ - [SecurityGroupResourceSpec](#securitygroupresourcespec) - [SecurityGroupRule](#securitygrouprule) - [SubnetFilter](#subnetfilter) +- [SubnetPoolFilter](#subnetpoolfilter) - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) - [TrunkResourceSpec](#trunkresourcespec) @@ -2564,6 +2568,7 @@ _Appears in:_ - [NetworkResourceStatus](#networkresourcestatus) - [PortResourceStatus](#portresourcestatus) - [SecurityGroupResourceStatus](#securitygroupresourcestatus) +- [SubnetPoolResourceStatus](#subnetpoolresourcestatus) - [SubnetResourceStatus](#subnetresourcestatus) - [TrunkResourceStatus](#trunkresourcestatus) @@ -2598,6 +2603,7 @@ _Appears in:_ - [SecurityGroupFilter](#securitygroupfilter) - [SecurityGroupResourceSpec](#securitygroupresourcespec) - [SubnetFilter](#subnetfilter) +- [SubnetPoolFilter](#subnetpoolfilter) - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) - [TrunkResourceSpec](#trunkresourcespec) @@ -4766,9 +4772,20 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| -| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `addressScopeRef` _[KubernetesNameRef](#kubernetesnameref)_ | addressScopeRef is a reference to the ORC AddressScope which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `minPrefixLength` _integer_ | minPrefixLength allows filtering the subnet pool list result by
the smallest prefix that can be allocated from a subnet pool. | | Optional: \{\}
| +| `maxPrefixLength` _integer_ | maxPrefixLength allows filtering the subnet pool list result by
the maximum prefix size that can be allocated from the subnet
pool. | | Optional: \{\}
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. It can be either 4 or 6 | | Enum: [4 6]
Optional: \{\}
| +| `shared` _boolean_ | shared allows filtering the list result based on whether the
resource is shared across all projects. This field is
admin-only. | | Optional: \{\}
| +| `defaultPrefixLength` _integer_ | defaultPrefixLength allows filtering the subnet pool list
result by the size of the prefix to allocate when the cidr or
prefixlen attributes are omitted when you create the subnet. | | Optional: \{\}
| +| `isDefault` _boolean_ | isDefault allows filtering the subnet pool list result based on
if it is a default pool or not. | | Optional: \{\}
| +| `revisionNumber` _integer_ | revisionNumber allows filtering the list result by the revision
number of the resource. | | Optional: \{\}
| +| `tags` _[NeutronTag](#neutrontag) 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
Optional: \{\}
| +| `tagsAny` _[NeutronTag](#neutrontag) 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
Optional: \{\}
| +| `notTags` _[NeutronTag](#neutrontag) 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
Optional: \{\}
| +| `notTagsAny` _[NeutronTag](#neutrontag) 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
Optional: \{\}
| #### SubnetPoolImport @@ -4808,6 +4825,12 @@ _Appears in:_ | `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `addressScopeRef` _[KubernetesNameRef](#kubernetesnameref)_ | addressScopeRef is a reference to the ORC AddressScope which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `prefixes` _[CIDR](#cidr) array_ | prefixes is the list of subnet prefixes to assign to the subnet
pool. The API merges adjacent prefixes and treats them as a
single prefix. Each subnet prefix must be unique across all
subnet pools associated with address scope. | | Format: cidr
MaxItems: 64
MaxLength: 49
MinItems: 1
MinLength: 1
Optional: \{\}
Required: \{\}
| +| `minPrefixLength` _integer_ | minPrefixLength is the smallest prefix that can be allocated
from a subnet pool. For IPv4 subnet pools, default is 8. For
IPv6 subnet pools, default is 64. | | Minimum: 1
Required: \{\}
| +| `maxPrefixLength` _integer_ | maxPrefixLength is the maximum prefix size that can be allocated
from the subnet pool. For IPv4 subnet pools, default is 32. For
IPv6 subnet pools, default is 128. | | Minimum: 1
Required: \{\}
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all projects.
By default, it is false, and only administrative users can
change this value. | | Optional: \{\}
| +| `defaultPrefixLength` _integer_ | defaultPrefixLength is the size of the prefix to allocate when
the cidr or prefixlen attributes are omitted when you create
the subnet. Default is MinPrefixLength. | | Optional: \{\}
| +| `isDefault` _boolean_ | isDefault defines whether the subnetpool is default pool or
not. | | Optional: \{\}
| #### SubnetPoolResourceStatus @@ -4827,6 +4850,18 @@ _Appears in:_ | `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| | `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| | `addressScopeID` _string_ | addressScopeID is the ID of the AddressScope to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `prefixes` _string array_ | prefixes is a list of prefixes to assign to the SubnetPool. | | MaxItems: 1024
items:MaxLength: 64
Optional: \{\}
| +| `defaultQuota` _integer_ | defaultQuota is a per-project quota on the prefix space that
can be allocated from the SubnetPool for project subnets. | | Optional: \{\}
| +| `minPrefixLength` _integer_ | minPrefixLength is the smallest prefix that can be allocated
from a subnet pool. | | Optional: \{\}
| +| `maxPrefixLength` _integer_ | maxPrefixLength is the maximum prefix size that can be
allocated from the subnet pool. | | Optional: \{\}
| +| `defaultPrefixLength` _integer_ | defaultPrefixLength is the size of the prefix to allocate when
the cidr or prefixlen attributes are omitted when you create
the subnet. | | Optional: \{\}
| +| `isDefault` _boolean_ | isDefault indicates whether the SubnetPool is the default pool
when creating subnets. | | Optional: \{\}
| +| `shared` _boolean_ | shared indicates whether the SubnetPool is shared across all projects. | | Optional: \{\}
| +| `ipVersion` _integer_ | ipVersion is the IP protocol version. It can be either 4 or 6 | | Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `createdAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | createdAt shows the date and time when the resource was created. The date and time stamp format is ISO 8601 | | Optional: \{\}
| +| `updatedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | updatedAt shows the date and time when the resource was updated. The date and time stamp format is ISO 8601 | | Optional: \{\}
| +| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | Optional: \{\}
| #### SubnetPoolSpec From 64d0da4083251a57ab347a49b420e6785c5c7a79 Mon Sep 17 00:00:00 2001 From: Winicius Allan Date: Thu, 9 Jul 2026 15:24:55 -0300 Subject: [PATCH 4/4] subnetpool: make shared immutable and fix apivalidation --- api/v1alpha1/subnetpool_types.go | 1 + api/v1alpha1/zz_generated.deepcopy.go | 9 +++++++++ .../zz_generated.subnetpool-resource.go | 15 +++++++++++++++ cmd/models-schema/zz_generated.openapi.go | 16 ++++++++++++++-- .../openstack.k-orc.cloud_subnetpools.yaml | 19 +++++++++++++++++++ internal/controllers/subnetpool/controller.go | 11 +++++++++-- .../subnetpool/zz_generated.adapter.go | 10 ++++++++++ .../api/v1alpha1/subnetpoolspec.go | 10 ++++++++++ .../api/v1alpha1/subnetpoolstatus.go | 16 +++++++++++++--- .../applyconfiguration/internal/internal.go | 6 ++++++ test/apivalidations/subnetpool_test.go | 5 ++++- website/docs/crd-reference.md | 2 ++ 12 files changed, 112 insertions(+), 8 deletions(-) diff --git a/api/v1alpha1/subnetpool_types.go b/api/v1alpha1/subnetpool_types.go index d663d5c3f..483794a9d 100644 --- a/api/v1alpha1/subnetpool_types.go +++ b/api/v1alpha1/subnetpool_types.go @@ -75,6 +75,7 @@ type SubnetPoolResourceSpec struct { // By default, it is false, and only administrative users can // change this value. // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="shared is immutable" Shared *bool `json:"shared,omitempty"` // defaultPrefixLength is the size of the prefix to allocate when diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b584afeba..b84d50101 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -6679,6 +6679,11 @@ func (in *SubnetPoolSpec) DeepCopyInto(out *SubnetPoolSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -6712,6 +6717,10 @@ func (in *SubnetPoolStatus) DeepCopyInto(out *SubnetPoolStatus) { *out = new(SubnetPoolResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetPoolStatus. diff --git a/api/v1alpha1/zz_generated.subnetpool-resource.go b/api/v1alpha1/zz_generated.subnetpool-resource.go index fb592254a..3f24717c9 100644 --- a/api/v1alpha1/zz_generated.subnetpool-resource.go +++ b/api/v1alpha1/zz_generated.subnetpool-resource.go @@ -75,6 +75,14 @@ type SubnetPoolSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type SubnetPoolStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *SubnetPoolResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &SubnetPool{} diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 2bb123650..b55bbcada 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -12599,6 +12599,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolSpec(ref com Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -12611,7 +12617,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolSpec(ref com }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceSpec"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -12659,11 +12665,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetPoolStatus(ref c Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetPoolResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml b/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml index 6bf448104..6a4abf613 100644 --- a/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_subnetpools.yaml @@ -348,10 +348,21 @@ spec: By default, it is false, and only administrative users can change this value. type: boolean + x-kubernetes-validations: + - message: shared is immutable + rule: self == oldSelf required: - maxPrefixLength - minPrefixLength type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -451,6 +462,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/internal/controllers/subnetpool/controller.go b/internal/controllers/subnetpool/controller.go index 49fce022d..5fc438ca6 100644 --- a/internal/controllers/subnetpool/controller.go +++ b/internal/controllers/subnetpool/controller.go @@ -19,6 +19,7 @@ package subnetpool import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -41,16 +42,22 @@ const controllerName = "subnetpool" type subnetpoolReconcilerConstructor struct { scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return subnetpoolReconcilerConstructor{scopeFactory: scopeFactory} + return &subnetpoolReconcilerConstructor{scopeFactory: scopeFactory} } func (subnetpoolReconcilerConstructor) GetName() string { return controllerName } +func (c *subnetpoolReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + + var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.SubnetPoolList, *orcv1alpha1.Project]( "spec.resource.projectRef", func(subnetpool *orcv1alpha1.SubnetPool) []string { @@ -151,6 +158,6 @@ func (c subnetpoolReconcilerConstructor) SetupWithManager(ctx context.Context, m return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, subnetpoolHelperFactory{}, subnetpoolStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, subnetpoolHelperFactory{}, subnetpoolStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/subnetpool/zz_generated.adapter.go b/internal/controllers/subnetpool/zz_generated.adapter.go index 31912efdf..9334b310a 100644 --- a/internal/controllers/subnetpool/zz_generated.adapter.go +++ b/internal/controllers/subnetpool/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package subnetpool import ( + 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" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go index 2ec290331..dd5507836 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // SubnetPoolSpecApplyConfiguration represents a declarative configuration of the SubnetPoolSpec type for use @@ -29,6 +30,7 @@ type SubnetPoolSpecApplyConfiguration struct { Resource *SubnetPoolResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *SubnetPoolSpecApplyConfiguration) WithManagedOptions(value *ManagedOpti return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *SubnetPoolSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *SubnetPoolSpecApplyConfiguration { + b.ResyncPeriod = &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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go index ba58679ca..92013ee19 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetpoolstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // SubnetPoolStatusApplyConfiguration represents a declarative configuration of the SubnetPoolStatus type for use // with apply. type SubnetPoolStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *SubnetPoolResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *SubnetPoolResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // SubnetPoolStatusApplyConfiguration constructs a declarative configuration of the SubnetPoolStatus type for use with @@ -64,3 +66,11 @@ func (b *SubnetPoolStatusApplyConfiguration) WithResource(value *SubnetPoolResou b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *SubnetPoolStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *SubnetPoolStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 68272306c..a54c156c6 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3841,6 +3841,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolStatus map: fields: @@ -3855,6 +3858,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetPoolResourceStatus diff --git a/test/apivalidations/subnetpool_test.go b/test/apivalidations/subnetpool_test.go index d20ebdf6b..106e3cf02 100644 --- a/test/apivalidations/subnetpool_test.go +++ b/test/apivalidations/subnetpool_test.go @@ -41,7 +41,10 @@ func subnetpoolStub(namespace *corev1.Namespace) *orcv1alpha1.SubnetPool { } func testSubnetPoolResource() *applyconfigv1alpha1.SubnetPoolResourceSpecApplyConfiguration { - return applyconfigv1alpha1.SubnetPoolResourceSpec() + return applyconfigv1alpha1.SubnetPoolResourceSpec(). + WithPrefixes(orcv1alpha1.CIDR("192.168.1.0/24")). + WithMinPrefixLength(8). + WithMaxPrefixLength(32) } func baseSubnetPoolPatch(obj client.Object) *applyconfigv1alpha1.SubnetPoolApplyConfiguration { diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index afe8daa8f..9826f4e72 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -4881,6 +4881,7 @@ _Appears in:_ | `resource` _[SubnetPoolResourceSpec](#subnetpoolresourcespec)_ | 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: \{\}
| | `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]
Optional: \{\}
| | `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4900,6 +4901,7 @@ _Appears in:_ | `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
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[SubnetPoolResourceStatus](#subnetpoolresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### SubnetResourceSpec