From b094882bd9675d318b0cabb5430c97d9771c50a5 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Thu, 18 Dec 2025 11:59:59 -0300 Subject: [PATCH 001/121] port: add macaddress field Signed-off-by: Winicius Silva --- api/v1alpha1/port_types.go | 10 ++++++++++ cmd/models-schema/zz_generated.openapi.go | 14 ++++++++++++++ config/crd/bases/openstack.k-orc.cloud_ports.yaml | 8 ++++++++ internal/controllers/port/actuator.go | 2 ++ internal/controllers/port/status.go | 3 ++- .../port/tests/port-create-full/00-assert.yaml | 2 +- .../tests/port-create-full/00-create-resource.yaml | 1 + .../port/tests/port-import/00-import-resource.yaml | 1 + .../port/tests/port-import/02-assert.yaml | 1 + .../port/tests/port-import/02-create-resource.yaml | 1 + .../applyconfiguration/api/v1alpha1/portfilter.go | 9 +++++++++ .../api/v1alpha1/portresourcespec.go | 9 +++++++++ .../applyconfiguration/internal/internal.go | 6 ++++++ website/docs/crd-reference.md | 2 ++ 14 files changed, 67 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 868748c17..22ca3ed77 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -41,6 +41,11 @@ type PortFilter struct { // +optional AdminStateUp *bool `json:"adminStateUp,omitempty"` + // macAddress is the MAC address of the port. + // +kubebuilder:validation:MaxLength=32 + // +optional + MACAddress string `json:"macAddress,omitempty"` + FilterByNeutronTags `json:",inline"` } @@ -170,6 +175,11 @@ type PortResourceSpec struct { // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // macAddress is the MAC address of the port. + // +kubebuilder:validation:MaxLength=32 + // +optional + MACAddress string `json:"macAddress,omitempty"` } type PortResourceStatus struct { diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 8eab33c2d..37432d6b8 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -4540,6 +4540,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortFilter(ref common. Format: "", }, }, + "macAddress": { + SchemaProps: spec.SchemaProps{ + Description: "macAddress is the MAC address of the port.", + Type: []string{"string"}, + Format: "", + }, + }, "tags": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -4895,6 +4902,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c Format: "", }, }, + "macAddress": { + SchemaProps: spec.SchemaProps{ + Description: "macAddress is the MAC address of the port.", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"networkRef"}, }, diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index 64c66bbf5..6b96e097a 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -105,6 +105,10 @@ spec: maxLength: 255 minLength: 1 type: string + macAddress: + description: macAddress is the MAC address of the port. + maxLength: 32 + type: string name: description: name of the existing resource maxLength: 255 @@ -294,6 +298,10 @@ spec: maxLength: 255 minLength: 1 type: string + macAddress: + description: macAddress is the MAC address of the port. + maxLength: 32 + type: string name: description: name is a human-readable name of the port. If not set, the object's name will be used. diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 570645623..61c23c7b5 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -141,6 +141,7 @@ func (actuator portActuator) ListOSResourcesForImport(ctx context.Context, obj o NotTags: tags.Join(filter.NotTags), NotTagsAny: tags.Join(filter.NotTagsAny), AdminStateUp: filter.AdminStateUp, + MACAddress: filter.MACAddress, } return actuator.osClient.ListPort(ctx, listOpts), nil @@ -197,6 +198,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha Description: string(ptr.Deref(resource.Description, "")), ProjectID: projectID, AdminStateUp: resource.AdminStateUp, + MACAddress: resource.MACAddress, } if len(resource.AllowedAddressPairs) > 0 { diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index d740caac0..0a15c7a60 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -73,7 +73,8 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou WithRevisionNumber(int64(osResource.RevisionNumber)). WithCreatedAt(metav1.NewTime(osResource.CreatedAt)). WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)). - WithAdminStateUp(osResource.AdminStateUp) + WithAdminStateUp(osResource.AdminStateUp). + WithMACAddress(osResource.MACAddress) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) diff --git a/internal/controllers/port/tests/port-create-full/00-assert.yaml b/internal/controllers/port/tests/port-create-full/00-assert.yaml index 3f2ea0750..3c866dbb6 100644 --- a/internal/controllers/port/tests/port-create-full/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-full/00-assert.yaml @@ -15,6 +15,7 @@ status: propagateUplinkStatus: false status: DOWN vnicType: direct + macAddress: fa:16:3e:23:fd:d7 tags: - tag1 --- @@ -41,7 +42,6 @@ assertAll: - celExpr: "port.status.id != ''" - celExpr: "port.status.resource.createdAt != ''" - celExpr: "port.status.resource.updatedAt != ''" - - celExpr: "port.status.resource.macAddress != ''" - celExpr: "port.status.resource.revisionNumber > 0" - celExpr: "port.status.resource.fixedIPs[0].subnetID == subnet.status.id" - celExpr: "port.status.resource.fixedIPs[0].ip == '192.168.155.122'" diff --git a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml index 31174591f..1721a83a1 100644 --- a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml +++ b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml @@ -84,3 +84,4 @@ spec: portSecurity: Enabled vnicType: direct projectRef: port-create-full + macAddress: fa:16:3e:23:fd:d7 diff --git a/internal/controllers/port/tests/port-import/00-import-resource.yaml b/internal/controllers/port/tests/port-import/00-import-resource.yaml index bed7543ba..0b58377dc 100644 --- a/internal/controllers/port/tests/port-import/00-import-resource.yaml +++ b/internal/controllers/port/tests/port-import/00-import-resource.yaml @@ -13,5 +13,6 @@ spec: name: port-import-external description: Port from "port-import" test adminStateUp: false + macAddress: fa:16:3e:23:fd:d7 tags: - tag1 diff --git a/internal/controllers/port/tests/port-import/02-assert.yaml b/internal/controllers/port/tests/port-import/02-assert.yaml index ff0745560..3ef560d86 100644 --- a/internal/controllers/port/tests/port-import/02-assert.yaml +++ b/internal/controllers/port/tests/port-import/02-assert.yaml @@ -31,5 +31,6 @@ status: name: port-import-external description: Port from "port-import" test adminStateUp: false + macAddress: fa:16:3e:23:fd:d7 tags: - tag1 diff --git a/internal/controllers/port/tests/port-import/02-create-resource.yaml b/internal/controllers/port/tests/port-import/02-create-resource.yaml index 838d1d0d6..bbd3842bb 100644 --- a/internal/controllers/port/tests/port-import/02-create-resource.yaml +++ b/internal/controllers/port/tests/port-import/02-create-resource.yaml @@ -12,5 +12,6 @@ spec: networkRef: port-import description: Port from "port-import" test adminStateUp: false + macAddress: fa:16:3e:23:fd:d7 tags: - tag1 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go index e1732f652..ab6d6e18d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go @@ -30,6 +30,7 @@ type PortFilterApplyConfiguration struct { NetworkRef *apiv1alpha1.KubernetesNameRef `json:"networkRef,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` AdminStateUp *bool `json:"adminStateUp,omitempty"` + MACAddress *string `json:"macAddress,omitempty"` FilterByNeutronTagsApplyConfiguration `json:",inline"` } @@ -79,6 +80,14 @@ func (b *PortFilterApplyConfiguration) WithAdminStateUp(value bool) *PortFilterA return b } +// WithMACAddress sets the MACAddress field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MACAddress field is set to the value of the last call. +func (b *PortFilterApplyConfiguration) WithMACAddress(value string) *PortFilterApplyConfiguration { + b.MACAddress = &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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index 67351b05c..ac7d4ed07 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -36,6 +36,7 @@ type PortResourceSpecApplyConfiguration struct { VNICType *string `json:"vnicType,omitempty"` PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + MACAddress *string `json:"macAddress,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -145,3 +146,11 @@ func (b *PortResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.Ku b.ProjectRef = &value return b } + +// WithMACAddress sets the MACAddress field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the MACAddress field is set to the value of the last call. +func (b *PortResourceSpecApplyConfiguration) WithMACAddress(value string) *PortResourceSpecApplyConfiguration { + b.MACAddress = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 5b5cb5142..e3cbf8592 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1244,6 +1244,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: description type: scalar: string + - name: macAddress + type: + scalar: string - name: name type: scalar: string @@ -1330,6 +1333,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: description type: scalar: string + - name: macAddress + type: + scalar: string - name: name type: scalar: string diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 23b3b2403..da7cd698d 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2070,6 +2070,7 @@ _Appears in:_ | `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this port is associated with. | | MaxLength: 253
MinLength: 1
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| | `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). | | | +| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| | `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
| | `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
| | `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
| @@ -2169,6 +2170,7 @@ _Appears in:_ | `vnicType` _string_ | vnicType specifies the type of vNIC which this port should be
attached to. This is used to determine which mechanism driver(s) to
be used to bind the port. The valid values are normal, macvtap,
direct, baremetal, direct-physical, virtio-forwarder, smart-nic and
remote-managed, although these values will not be validated in this
API to ensure compatibility with future neutron changes or custom
implementations. What type of vNIC is actually available depends on
deployments. If not specified, the Neutron default value is used. | | MaxLength: 64
| | `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| +| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| #### PortResourceStatus From 152961177d43ae10603a91b0d2e6e8f354e7303c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 2 Jan 2026 12:20:46 +0100 Subject: [PATCH 002/121] Get orc resources via their full names This avoids issues when the resource name conflicts with existing resources and we can't use shortcuts in kubectl commands (e.g. service or role). --- .../data/tests/dependency/02-delete-dependencies.yaml.template | 2 +- .../data/tests/dependency/03-assert.yaml.template | 2 +- .../data/tests/import-dependency/03-assert.yaml.template | 2 +- .../03-delete-import-dependencies.yaml.template | 2 +- .../data/tests/import-dependency/04-assert.yaml.template | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/scaffold-controller/data/tests/dependency/02-delete-dependencies.yaml.template b/cmd/scaffold-controller/data/tests/dependency/02-delete-dependencies.yaml.template index 6afc372ef..a5e38546d 100644 --- a/cmd/scaffold-controller/data/tests/dependency/02-delete-dependencies.yaml.template +++ b/cmd/scaffold-controller/data/tests/dependency/02-delete-dependencies.yaml.template @@ -5,7 +5,7 @@ kind: TestStep commands: # We expect the deletion to hang due to the finalizer, so use --wait=false {{- range .AllCreateDependencies }} - - command: kubectl delete {{ . | lower }} {{ $packageName }}-dependency --wait=false + - command: kubectl delete {{ . | lower }}.openstack.k-orc.cloud {{ $packageName }}-dependency --wait=false namespaced: true {{- end }} - command: kubectl delete secret {{ $packageName }}-dependency --wait=false diff --git a/cmd/scaffold-controller/data/tests/dependency/03-assert.yaml.template b/cmd/scaffold-controller/data/tests/dependency/03-assert.yaml.template index fa188a9d1..aa96bb1aa 100644 --- a/cmd/scaffold-controller/data/tests/dependency/03-assert.yaml.template +++ b/cmd/scaffold-controller/data/tests/dependency/03-assert.yaml.template @@ -5,7 +5,7 @@ kind: TestAssert commands: # Dependencies that were prevented deletion before should now be gone {{- range .AllCreateDependencies }} -- script: "! kubectl get {{ . | lower }} {{ $packageName }}-dependency --namespace $NAMESPACE" +- script: "! kubectl get {{ . | lower }}.openstack.k-orc.cloud {{ $packageName }}-dependency --namespace $NAMESPACE" skipLogOutput: true {{- end }} - script: "! kubectl get secret {{ $packageName }}-dependency --namespace $NAMESPACE" diff --git a/cmd/scaffold-controller/data/tests/import-dependency/03-assert.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/03-assert.yaml.template index c4c2e4879..0f1e4487a 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/03-assert.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/03-assert.yaml.template @@ -4,6 +4,6 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: {{- range .ImportDependencies }} -- script: "! kubectl get {{ . | lower }} {{ $packageName }}-import-dependency --namespace $NAMESPACE" +- script: "! kubectl get {{ . | lower }}.openstack.k-orc.cloud {{ $packageName }}-import-dependency --namespace $NAMESPACE" skipLogOutput: true {{- end }} diff --git a/cmd/scaffold-controller/data/tests/import-dependency/03-delete-import-dependencies.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/03-delete-import-dependencies.yaml.template index 45c3d2658..184f2f866 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/03-delete-import-dependencies.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/03-delete-import-dependencies.yaml.template @@ -5,6 +5,6 @@ kind: TestStep commands: # We should be able to delete the import dependencies {{- range .ImportDependencies }} - - command: kubectl delete {{ . | lower }} {{ $packageName }}-import-dependency + - command: kubectl delete {{ . | lower }}.openstack.k-orc.cloud {{ $packageName }}-import-dependency namespaced: true {{- end }} diff --git a/cmd/scaffold-controller/data/tests/import-dependency/04-assert.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/04-assert.yaml.template index e7f733ae9..cab39b2e5 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/04-assert.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/04-assert.yaml.template @@ -2,5 +2,5 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -- script: "! kubectl get {{ .PackageName }} {{ .PackageName }}-import-dependency --namespace $NAMESPACE" +- script: "! kubectl get {{ .PackageName }}.openstack.k-orc.cloud {{ .PackageName }}-import-dependency --namespace $NAMESPACE" skipLogOutput: true From 28691b370f176c0ddb8a56a74c7fe113fdcd1055 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Fri, 2 Jan 2026 20:47:24 -0300 Subject: [PATCH 003/121] remove duplicated MACAddress and AdminStateUp status writing --- internal/controllers/port/status.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index 0a15c7a60..e193b9fb1 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -72,9 +72,7 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou WithPortSecurityEnabled(osResource.PortSecurityEnabled). WithRevisionNumber(int64(osResource.RevisionNumber)). WithCreatedAt(metav1.NewTime(osResource.CreatedAt)). - WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)). - WithAdminStateUp(osResource.AdminStateUp). - WithMACAddress(osResource.MACAddress) + WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) From b703e72d8bd1c977b82e29d6cf33cda7e2ffe662 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Tue, 6 Jan 2026 11:33:54 -0300 Subject: [PATCH 004/121] Port: add binding:host field --- api/v1alpha1/port_types.go | 10 +++++++ cmd/models-schema/zz_generated.openapi.go | 14 +++++++++ .../bases/openstack.k-orc.cloud_ports.yaml | 8 +++++ internal/controllers/port/actuator.go | 11 +++++++ internal/controllers/port/status.go | 3 +- .../tests/port-create-full/00-assert.yaml | 3 +- .../port-create-full/00-create-resource.yaml | 3 +- .../port/tests/port-update/00-assert.yaml | 30 +++++++++++++++++++ .../port-update/00-minimal-resource.yaml | 14 +++++++++ .../port/tests/port-update/01-assert.yaml | 17 ++++++++++- .../port-update/01-updated-resource.yaml | 14 ++++++++- .../port-update/02-reverted-resource.yaml | 2 +- .../api/v1alpha1/portresourcespec.go | 9 ++++++ .../api/v1alpha1/portresourcestatus.go | 9 ++++++ .../applyconfiguration/internal/internal.go | 6 ++++ website/docs/crd-reference.md | 2 ++ 16 files changed, 149 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 22ca3ed77..9e51d0153 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -180,6 +180,11 @@ type PortResourceSpec struct { // +kubebuilder:validation:MaxLength=32 // +optional MACAddress string `json:"macAddress,omitempty"` + + // hostID is the ID of host where the port resides. + // +kubebuilder:validation:MaxLength=36 + // +optional + HostID string `json:"hostID,omitempty"` } type PortResourceStatus struct { @@ -272,6 +277,11 @@ type PortResourceStatus struct { // +optional PortSecurityEnabled *bool `json:"portSecurityEnabled,omitempty"` + // hostID is the ID of host where the port resides. + // +kubebuilder:validation:MaxLength=128 + // +optional + HostID string `json:"hostID,omitempty"` + NeutronStatusMetadata `json:",inline"` } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 37432d6b8..16bf94279 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -4909,6 +4909,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c Format: "", }, }, + "hostID": { + SchemaProps: spec.SchemaProps{ + Description: "hostID is the ID of host where the port resides.", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"networkRef"}, }, @@ -5086,6 +5093,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref Format: "", }, }, + "hostID": { + SchemaProps: spec.SchemaProps{ + Description: "hostID is the ID of host where the port resides.", + 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", diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index 6b96e097a..c14173d71 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -298,6 +298,10 @@ spec: maxLength: 255 minLength: 1 type: string + hostID: + description: hostID is the ID of host where the port resides. + maxLength: 36 + type: string macAddress: description: macAddress is the MAC address of the port. maxLength: 32 @@ -561,6 +565,10 @@ spec: maxItems: 128 type: array x-kubernetes-list-type: atomic + hostID: + description: hostID is the ID of host where the port resides. + maxLength: 128 + type: string macAddress: description: macAddress is the MAC address of the port. maxLength: 1024 diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 61c23c7b5..692474e41 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -254,6 +254,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha portsBindingOpts := portsbinding.CreateOptsExt{ CreateOptsBuilder: createOpts, VNICType: resource.VNICType, + HostID: resource.HostID, } portSecurityOpts := portsecurity.PortCreateOptsExt{ @@ -504,6 +505,16 @@ func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resou } } } + + if resource.HostID != "" { + if resource.HostID != osResource.HostID { + updateOpts = &portsbinding.UpdateOptsExt{ + UpdateOptsBuilder: updateOpts, + HostID: &resource.HostID, + } + } + } + return updateOpts } diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index e193b9fb1..379e91e70 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -72,7 +72,8 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou WithPortSecurityEnabled(osResource.PortSecurityEnabled). WithRevisionNumber(int64(osResource.RevisionNumber)). WithCreatedAt(metav1.NewTime(osResource.CreatedAt)). - WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)) + WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)). + WithHostID(osResource.HostID) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) diff --git a/internal/controllers/port/tests/port-create-full/00-assert.yaml b/internal/controllers/port/tests/port-create-full/00-assert.yaml index 3c866dbb6..f026eea4a 100644 --- a/internal/controllers/port/tests/port-create-full/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-full/00-assert.yaml @@ -14,8 +14,9 @@ status: portSecurityEnabled: true propagateUplinkStatus: false status: DOWN - vnicType: direct + vnicType: macvtap macAddress: fa:16:3e:23:fd:d7 + hostID: devstack tags: - tag1 --- diff --git a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml index 1721a83a1..bbb52641d 100644 --- a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml +++ b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml @@ -82,6 +82,7 @@ spec: - subnetRef: port-create-full ip: 192.168.155.122 portSecurity: Enabled - vnicType: direct + vnicType: macvtap projectRef: port-create-full macAddress: fa:16:3e:23:fd:d7 + hostID: devstack diff --git a/internal/controllers/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index fef380932..6ec7e451d 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -6,6 +6,10 @@ resourceRefs: kind: port name: port-update ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: port + name: port-update-admin + ref: portAdmin assertAll: - celExpr: "port.status.id != ''" - celExpr: "port.status.resource.createdAt != ''" @@ -13,6 +17,9 @@ assertAll: - celExpr: "port.status.resource.macAddress != ''" - celExpr: "!has(port.status.resource.fixedIPs)" - celExpr: "!has(port.status.resource.description)" + # Following the network API reference, the default value for + # hostID field is an empty string. + - celExpr: "portAdmin.status.resource.hostID == ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port @@ -36,3 +43,26 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-update-admin +status: + resource: + name: port-update-admin + adminStateUp: true + portSecurityEnabled: true + propagateUplinkStatus: false + revisionNumber: 1 + status: DOWN + vnicType: normal + 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/port/tests/port-update/00-minimal-resource.yaml b/internal/controllers/port/tests/port-update/00-minimal-resource.yaml index d1242e77f..03bbe59c3 100644 --- a/internal/controllers/port/tests/port-update/00-minimal-resource.yaml +++ b/internal/controllers/port/tests/port-update/00-minimal-resource.yaml @@ -12,3 +12,17 @@ spec: portSecurity: Disabled # Need to set the default values to revert them correctly in the 02-revert-resource step. vnicType: normal +--- +# This port is intended to be used only to test fields editable +# by admin users +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-update-admin +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: port-update diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index ef7850e61..1bcaf2d7f 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -48,4 +48,19 @@ status: reason: Success - type: Progressing status: "False" - reason: Success \ No newline at end of file + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-update-admin +status: + resource: + hostID: devstack + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/port/tests/port-update/01-updated-resource.yaml b/internal/controllers/port/tests/port-update/01-updated-resource.yaml index 796726336..107b4ab5d 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -19,4 +19,16 @@ spec: tags: - tag1 vnicType: direct - portSecurity: Enabled \ No newline at end of file + portSecurity: Enabled +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-update-admin +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + hostID: devstack diff --git a/internal/controllers/port/tests/port-update/02-reverted-resource.yaml b/internal/controllers/port/tests/port-update/02-reverted-resource.yaml index ec043aae6..2c6c253ff 100644 --- a/internal/controllers/port/tests/port-update/02-reverted-resource.yaml +++ b/internal/controllers/port/tests/port-update/02-reverted-resource.yaml @@ -4,4 +4,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - command: kubectl replace -f 00-minimal-resource.yaml - namespaced: true \ No newline at end of file + namespaced: true diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index ac7d4ed07..ba3979e9e 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -37,6 +37,7 @@ type PortResourceSpecApplyConfiguration struct { PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` MACAddress *string `json:"macAddress,omitempty"` + HostID *string `json:"hostID,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -154,3 +155,11 @@ func (b *PortResourceSpecApplyConfiguration) WithMACAddress(value string) *PortR b.MACAddress = &value return b } + +// WithHostID sets the HostID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HostID field is set to the value of the last call. +func (b *PortResourceSpecApplyConfiguration) WithHostID(value string) *PortResourceSpecApplyConfiguration { + b.HostID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go index 1fd734822..f4b34cad1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go @@ -41,6 +41,7 @@ type PortResourceStatusApplyConfiguration struct { PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` VNICType *string `json:"vnicType,omitempty"` PortSecurityEnabled *bool `json:"portSecurityEnabled,omitempty"` + HostID *string `json:"hostID,omitempty"` NeutronStatusMetadataApplyConfiguration `json:",inline"` } @@ -192,6 +193,14 @@ func (b *PortResourceStatusApplyConfiguration) WithPortSecurityEnabled(value boo return b } +// WithHostID sets the HostID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HostID field is set to the value of the last call. +func (b *PortResourceStatusApplyConfiguration) WithHostID(value string) *PortResourceStatusApplyConfiguration { + b.HostID = &value + 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. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index e3cbf8592..4b97cb3ea 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1333,6 +1333,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: description type: scalar: string + - name: hostID + type: + scalar: string - name: macAddress type: scalar: string @@ -1393,6 +1396,9 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FixedIPStatus elementRelationship: atomic + - name: hostID + type: + scalar: string - name: macAddress type: scalar: string diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index da7cd698d..38766bd7b 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2171,6 +2171,7 @@ _Appears in:_ | `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| | `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| +| `hostID` _string_ | hostID is the ID of host where the port resides. | | MaxLength: 36
| #### PortResourceStatus @@ -2202,6 +2203,7 @@ _Appears in:_ | `propagateUplinkStatus` _boolean_ | propagateUplinkStatus represents the uplink status propagation of
the port. | | | | `vnicType` _string_ | vnicType is the type of vNIC which this port is attached to. | | MaxLength: 64
| | `portSecurityEnabled` _boolean_ | portSecurityEnabled indicates whether port security is enabled or not. | | | +| `hostID` _string_ | hostID is the ID of host where the port resides. | | MaxLength: 128
| | `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 | | | | `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 | | | | `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | From a57831bba0b003198f2844ec938173da54822058 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 24 Dec 2025 17:41:55 +0200 Subject: [PATCH 005/121] globalize get dependency helper - create a dependency helper replacing repetative parts of code - update template to use it --- .../data/controller/actuator.go.template | 34 +++------- internal/util/dependency/helpers.go | 67 +++++++++++++++++++ 2 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 internal/util/dependency/helpers.go diff --git a/cmd/scaffold-controller/data/controller/actuator.go.template b/cmd/scaffold-controller/data/controller/actuator.go.template index dc083e79d..55c64ff3b 100644 --- a/cmd/scaffold-controller/data/controller/actuator.go.template +++ b/cmd/scaffold-controller/data/controller/actuator.go.template @@ -25,9 +25,6 @@ import ( "{{ .GophercloudModule }}" corev1 "k8s.io/api/core/v1" -{{- if len .ImportDependencies }} - apierrors "k8s.io/apimachinery/pkg/api/errors" -{{- end }} "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,6 +34,9 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" +{{- if len .ImportDependencies }} + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" +{{- end }} orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" ) @@ -106,24 +106,12 @@ func (actuator {{ .PackageName }}Actuator) ListOSResourcesForImport(ctx context. var reconcileStatus progress.ReconcileStatus {{- range .ImportDependencies }} {{ $depNameCamelCase := . | camelCase }} - {{ $depNameCamelCase }} := &orcv1alpha1.{{ . }}{} - if filter.{{ . }}Ref != nil { - {{ $depNameCamelCase }}Key := client.ObjectKey{Name: string(*filter.{{ . }}Ref), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, {{ $depNameCamelCase }}Key, {{ $depNameCamelCase }}); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("{{ . }}", {{ $depNameCamelCase }}Key.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching {{ $depNameCamelCase }} %s: %w", {{ $depNameCamelCase }}Key.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable({{ $depNameCamelCase }}) || {{ $depNameCamelCase }}.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("{{ . }}", {{ $depNameCamelCase }}Key.Name, progress.WaitingOnReady)) - } - } - } + {{ $depNameCamelCase }}, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.{{ . }}Ref, "{{ . }}", + func(dep *orcv1alpha1.{{ . }}) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) {{- end }} if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { @@ -135,12 +123,12 @@ func (actuator {{ .PackageName }}Actuator) ListOSResourcesForImport(ctx context. Name: string(ptr.Deref(filter.Name, "")), Description: string(ptr.Deref(filter.Description, "")), {{- range .ImportDependencies }} - {{ . }}: ptr.Deref({{ . | camelCase }}.Status.ID, ""), + {{ . }}ID: ptr.Deref({{ . | camelCase }}.Status.ID, ""), {{- end }} // TODO(scaffolding): Add more import filters } - return actuator.osClient.List{{ .Kind }}s(ctx, listOpts), nil + return actuator.osClient.List{{ .Kind }}s(ctx, listOpts), {{ if len .ImportDependencies }}reconcileStatus{{ else }}nil{{ end }} } func (actuator {{ .PackageName }}Actuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { diff --git a/internal/util/dependency/helpers.go b/internal/util/dependency/helpers.go new file mode 100644 index 000000000..7590eca3c --- /dev/null +++ b/internal/util/dependency/helpers.go @@ -0,0 +1,67 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dependency + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" +) + +// FetchDependency fetches a resource by name and checks if it's ready. +// Unlike GetDependency on DeletionGuardDependency, this doesn't add finalizers +// and is suitable for one-off lookups like resolving refs in import filters. +// +// Always returns an object (empty struct if not found/ready/error) for safe field access. +// +// Returns: +// - The fetched object (empty struct if name is nil, not found, not ready, or on error) +// - ReconcileStatus indicating wait state or error (nil only if name is nil or object is ready) +func FetchDependency[TP DependencyType[T], T any]( + ctx context.Context, + k8sClient client.Client, + namespace string, + name *orcv1alpha1.KubernetesNameRef, + kind string, + isReady func(TP) bool, +) (TP, progress.ReconcileStatus) { + var obj TP = new(T) + + if name == nil { + return obj, nil + } + + objectKey := client.ObjectKey{Name: string(*name), Namespace: namespace} + + if err := k8sClient.Get(ctx, objectKey, obj); err != nil { + if apierrors.IsNotFound(err) { + return obj, progress.NewReconcileStatus().WaitingOnObject(kind, string(*name), progress.WaitingOnCreation) + } + return obj, progress.WrapError(fmt.Errorf("fetching %s %s: %w", kind, string(*name), err)) + } + + if !isReady(obj) { + return obj, progress.NewReconcileStatus().WaitingOnObject(kind, string(*name), progress.WaitingOnReady) + } + + return obj, nil +} From 153dba04b7caf303306ec532c6e99ea519c6f129 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Sun, 4 Jan 2026 14:18:38 +0200 Subject: [PATCH 006/121] refactor actuators to use global FetchDependency helper - Remove local getDependencyHelper and wrapper functions from server actuator - Use dependency.FetchDependency directly in CreateResource and ListOSResourcesForImport --- internal/controllers/floatingip/actuator.go | 82 +++++--------- internal/controllers/group/actuator.go | 28 ++--- internal/controllers/network/actuator.go | 28 ++--- internal/controllers/port/actuator.go | 52 +++------ internal/controllers/role/actuator.go | 28 ++--- internal/controllers/router/actuator.go | 28 ++--- .../controllers/securitygroup/actuator.go | 27 ++--- internal/controllers/server/actuator.go | 102 ++++++------------ internal/controllers/subnet/actuator.go | 51 +++------ internal/util/dependency/helpers.go | 3 +- 10 files changed, 129 insertions(+), 300 deletions(-) diff --git a/internal/controllers/floatingip/actuator.go b/internal/controllers/floatingip/actuator.go index 05a15a061..8a99c564e 100644 --- a/internal/controllers/floatingip/actuator.go +++ b/internal/controllers/floatingip/actuator.go @@ -18,7 +18,6 @@ package floatingip import ( "context" - "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/floatingips" @@ -27,10 +26,10 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" osclients "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" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -89,62 +88,29 @@ func (actuator floatingipActuator) ListOSResourcesForAdoption(ctx context.Contex func (actuator floatingipCreateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - network := &orcv1alpha1.Network{} - if filter.FloatingNetworkRef != nil { - networkKey := client.ObjectKey{Name: string(ptr.Deref(filter.FloatingNetworkRef, "")), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, networkKey, network); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching network %s: %w", networkKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(network) || network.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnReady)) - } - } - } - - port := &orcv1alpha1.Port{} - if filter.PortRef != nil { - portKey := client.ObjectKey{Name: string(ptr.Deref(filter.PortRef, "")), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, portKey, port); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Port", portKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching port %s: %w", portKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(port) || port.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Port", portKey.Name, progress.WaitingOnReady)) - } - } - } - - project := &orcv1alpha1.Project{} - if filter.ProjectRef != nil { - projectKey := client.ObjectKey{Name: string(*filter.ProjectRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, projectKey, project); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching project %s: %w", projectKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(project) || project.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnReady)) - } - } - } + network, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.FloatingNetworkRef, "Network", + func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + port, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.PortRef, "Port", + func(dep *orcv1alpha1.Port) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/group/actuator.go b/internal/controllers/group/actuator.go index 268cd2fd3..afdaad252 100644 --- a/internal/controllers/group/actuator.go +++ b/internal/controllers/group/actuator.go @@ -18,12 +18,10 @@ package group import ( "context" - "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/groups" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -33,6 +31,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" ) @@ -83,24 +82,13 @@ func (actuator groupActuator) ListOSResourcesForImport(ctx context.Context, obj var reconcileStatus progress.ReconcileStatus - domain := &orcv1alpha1.Domain{} - if filter.DomainRef != nil { - domainKey := client.ObjectKey{Name: string(*filter.DomainRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, domainKey, domain); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Domain", domainKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching domain %s: %w", domainKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(domain) || domain.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Domain", domainKey.Name, progress.WaitingOnReady)) - } - } - } + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/network/actuator.go b/internal/controllers/network/actuator.go index 8d7f1eeed..ffb7a147b 100644 --- a/internal/controllers/network/actuator.go +++ b/internal/controllers/network/actuator.go @@ -18,7 +18,6 @@ package network import ( "context" - "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/dns" @@ -27,7 +26,6 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,6 +35,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" "github.com/k-orc/openstack-resource-controller/v2/internal/util/tags" ) @@ -84,24 +83,13 @@ func (actuator networkActuator) ListOSResourcesForAdoption(ctx context.Context, func (actuator networkActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - project := &orcv1alpha1.Project{} - if filter.ProjectRef != nil { - projectKey := client.ObjectKey{Name: string(*filter.ProjectRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, projectKey, project); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching project %s: %w", projectKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(project) || project.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnReady)) - } - } - } + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 570645623..dab99f573 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -27,7 +27,6 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,6 +36,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" osclients "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" ) @@ -89,43 +89,21 @@ func (actuator portActuator) ListOSResourcesForAdoption(ctx context.Context, obj func (actuator portActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - network := &orcv1alpha1.Network{} - if filter.NetworkRef != "" { - networkKey := client.ObjectKey{Name: string(filter.NetworkRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, networkKey, network); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching network %s: %w", networkKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(network) || network.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnReady)) - } - } - } + network, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, &filter.NetworkRef, "Network", + func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project := &orcv1alpha1.Project{} - if filter.ProjectRef != nil { - projectKey := client.ObjectKey{Name: string(*filter.ProjectRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, projectKey, project); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching project %s: %w", projectKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(project) || project.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnReady)) - } - } - } + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/role/actuator.go b/internal/controllers/role/actuator.go index b278cdd8d..ba3be6b75 100644 --- a/internal/controllers/role/actuator.go +++ b/internal/controllers/role/actuator.go @@ -18,12 +18,11 @@ package role import ( "context" - "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,24 +81,13 @@ func (actuator roleActuator) ListOSResourcesForAdoption(ctx context.Context, orc func (actuator roleActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - domain := &orcv1alpha1.Domain{} - if filter.DomainRef != nil { - domainKey := client.ObjectKey{Name: string(*filter.DomainRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, domainKey, domain); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Domain", domainKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching domain %s: %w", domainKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(domain) || domain.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Domain", domainKey.Name, progress.WaitingOnReady)) - } - } - } + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/router/actuator.go b/internal/controllers/router/actuator.go index 04c1d491b..59768482e 100644 --- a/internal/controllers/router/actuator.go +++ b/internal/controllers/router/actuator.go @@ -18,12 +18,10 @@ package router import ( "context" - "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/routers" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -33,6 +31,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" osclients "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" ) @@ -84,24 +83,13 @@ func (actuator routerActuator) ListOSResourcesForAdoption(ctx context.Context, o func (actuator routerCreateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - project := &orcv1alpha1.Project{} - if filter.ProjectRef != nil { - projectKey := client.ObjectKey{Name: string(*filter.ProjectRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, projectKey, project); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching project %s: %w", projectKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(project) || project.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnReady)) - } - } - } + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index b7165e595..703f25c7c 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -29,10 +29,10 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" osclients "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" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" "k8s.io/utils/set" ctrl "sigs.k8s.io/controller-runtime" @@ -82,24 +82,13 @@ func (actuator securityGroupActuator) ListOSResourcesForAdoption(ctx context.Con func (actuator securityGroupActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - project := &orcv1alpha1.Project{} - if filter.ProjectRef != nil { - projectKey := client.ObjectKey{Name: string(*filter.ProjectRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, projectKey, project); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching project %s: %w", projectKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(project) || project.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnReady)) - } - } - } + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index 6a2118695..c0aefdb2d 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -29,7 +29,6 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/volumeattach" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -39,6 +38,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" "github.com/k-orc/openstack-resource-controller/v2/internal/util/tags" ) @@ -150,69 +150,6 @@ func (actuator serverActuator) ListOSResourcesForImport(ctx context.Context, obj return wrapServers(actuator.osClient.ListServers(ctx, listOpts)), nil } -// getDependencyHelper is a generic helper for fetching and validating dependencies -func getDependencyHelper[T client.Object]( - ctx context.Context, - k8sClient client.Client, - obj *orcv1alpha1.Server, - name string, - kind string, - isReady func(T) bool, - dep T, -) (T, progress.ReconcileStatus) { - objectKey := client.ObjectKey{Name: name, Namespace: obj.Namespace} - err := k8sClient.Get(ctx, objectKey, dep) - if apierrors.IsNotFound(err) { - return dep, progress.NewReconcileStatus().WaitingOnObject(kind, objectKey.Name, progress.WaitingOnCreation) - } else if err != nil { - return dep, progress.WrapError(fmt.Errorf("fetching %s %s: %w", kind, objectKey.Name, err)) - } else if !isReady(dep) { - return dep, progress.NewReconcileStatus().WaitingOnObject(kind, objectKey.Name, progress.WaitingOnReady) - } - return dep, progress.NewReconcileStatus() -} - -func (actuator serverActuator) getFlavorHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (*orcv1alpha1.Flavor, progress.ReconcileStatus) { - return getDependencyHelper(ctx, actuator.k8sClient, obj, string(resource.FlavorRef), "Flavor", func(f *orcv1alpha1.Flavor) bool { - return orcv1alpha1.IsAvailable(f) && f.Status.ID != nil - }, &orcv1alpha1.Flavor{}) -} - -func (actuator serverActuator) getServerGroupHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (*orcv1alpha1.ServerGroup, progress.ReconcileStatus) { - if resource.ServerGroupRef == nil { - return &orcv1alpha1.ServerGroup{}, progress.NewReconcileStatus() - } - return getDependencyHelper(ctx, actuator.k8sClient, obj, string(*resource.ServerGroupRef), "ServerGroup", func(sg *orcv1alpha1.ServerGroup) bool { - return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil - }, &orcv1alpha1.ServerGroup{}) -} - -func (actuator serverActuator) getKeypairHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (*orcv1alpha1.KeyPair, progress.ReconcileStatus) { - if resource.KeypairRef == nil { - return &orcv1alpha1.KeyPair{}, progress.NewReconcileStatus() - } - return getDependencyHelper(ctx, actuator.k8sClient, obj, string(*resource.KeypairRef), "KeyPair", func(kp *orcv1alpha1.KeyPair) bool { - return orcv1alpha1.IsAvailable(kp) && kp.Status.Resource != nil - }, &orcv1alpha1.KeyPair{}) -} - -func (actuator serverActuator) getUserDataHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) ([]byte, progress.ReconcileStatus) { - if resource.UserData == nil || resource.UserData.SecretRef == nil { - return nil, progress.NewReconcileStatus() - } - secret, reconcileStatus := getDependencyHelper(ctx, actuator.k8sClient, obj, string(*resource.UserData.SecretRef), "Secret", func(s *corev1.Secret) bool { - return true // Secrets don't have availability status - }, &corev1.Secret{}) - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { - return nil, reconcileStatus - } - userData, ok := secret.Data["value"] - if !ok { - return nil, progress.NewReconcileStatus().WithProgressMessage("User data secret does not contain \"value\" key") - } - return userData, progress.NewReconcileStatus() -} - func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.Server) (*osResourceT, progress.ReconcileStatus) { resource := obj.Spec.Resource if resource == nil { @@ -234,7 +171,11 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp image = dep } - flavor, flavorReconcileStatus := actuator.getFlavorHelper(ctx, obj, resource) + flavor, flavorReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + &resource.FlavorRef, "Flavor", + func(f *orcv1alpha1.Flavor) bool { return orcv1alpha1.IsAvailable(f) && f.Status.ID != nil }, + ) reconcileStatus = reconcileStatus.WithReconcileStatus(flavorReconcileStatus) portList := make([]servers.Network, len(resource.Ports)) @@ -265,14 +206,37 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp } } - serverGroup, serverGroupReconcileStatus := actuator.getServerGroupHelper(ctx, obj, resource) + serverGroup, serverGroupReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + resource.ServerGroupRef, "ServerGroup", + func(sg *orcv1alpha1.ServerGroup) bool { return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil }, + ) reconcileStatus = reconcileStatus.WithReconcileStatus(serverGroupReconcileStatus) - keypair, keypairReconcileStatus := actuator.getKeypairHelper(ctx, obj, resource) + keypair, keypairReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + resource.KeypairRef, "KeyPair", + func(kp *orcv1alpha1.KeyPair) bool { return orcv1alpha1.IsAvailable(kp) && kp.Status.Resource != nil }, + ) reconcileStatus = reconcileStatus.WithReconcileStatus(keypairReconcileStatus) - userData, userDataReconcileStatus := actuator.getUserDataHelper(ctx, obj, resource) - reconcileStatus = reconcileStatus.WithReconcileStatus(userDataReconcileStatus) + var userData []byte + if resource.UserData != nil { + secret, secretReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + resource.UserData.SecretRef, "Secret", + func(*corev1.Secret) bool { return true }, // Secrets don't have availability status + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus) + if secretReconcileStatus == nil { + var ok bool + userData, ok = secret.Data["value"] + if !ok { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.NewReconcileStatus().WithProgressMessage("User data secret does not contain \"value\" key")) + } + } + } if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/subnet/actuator.go b/internal/controllers/subnet/actuator.go index 951718ba6..f3e0e8dd4 100644 --- a/internal/controllers/subnet/actuator.go +++ b/internal/controllers/subnet/actuator.go @@ -37,6 +37,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" "github.com/k-orc/openstack-resource-controller/v2/internal/util/tags" ) @@ -86,43 +87,21 @@ func (actuator subnetActuator) ListOSResourcesForAdoption(ctx context.Context, o func (actuator subnetActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - network := &orcv1alpha1.Network{} - if filter.NetworkRef != "" { - networkKey := client.ObjectKey{Name: string(filter.NetworkRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, networkKey, network); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching network %s: %w", networkKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(network) || network.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Network", networkKey.Name, progress.WaitingOnReady)) - } - } - } + network, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, &filter.NetworkRef, "Network", + func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project := &orcv1alpha1.Project{} - if filter.ProjectRef != nil { - projectKey := client.ObjectKey{Name: string(*filter.ProjectRef), Namespace: obj.Namespace} - if err := actuator.k8sClient.Get(ctx, projectKey, project); err != nil { - if apierrors.IsNotFound(err) { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnCreation)) - } else { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WrapError(fmt.Errorf("fetching project %s: %w", projectKey.Name, err))) - } - } else { - if !orcv1alpha1.IsAvailable(project) || project.Status.ID == nil { - reconcileStatus = reconcileStatus.WithReconcileStatus( - progress.WaitingOnObject("Project", projectKey.Name, progress.WaitingOnReady)) - } - } - } + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/util/dependency/helpers.go b/internal/util/dependency/helpers.go index 7590eca3c..be9caa005 100644 --- a/internal/util/dependency/helpers.go +++ b/internal/util/dependency/helpers.go @@ -21,6 +21,7 @@ import ( "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" @@ -46,7 +47,7 @@ func FetchDependency[TP DependencyType[T], T any]( ) (TP, progress.ReconcileStatus) { var obj TP = new(T) - if name == nil { + if ptr.Deref(name, "") == "" { return obj, nil } From d9608e4182af6b28c9a6c319dd25e614b90f403b Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 7 Jan 2026 10:08:20 -0300 Subject: [PATCH 007/121] bump: gophercloud to v2.10.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0562d961f..419ce1cbe 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-logr/logr v1.4.3 - github.com/gophercloud/gophercloud/v2 v2.9.0 + github.com/gophercloud/gophercloud/v2 v2.10.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 github.com/onsi/ginkgo/v2 v2.27.3 github.com/onsi/gomega v1.38.3 diff --git a/go.sum b/go.sum index 0d31f1ca9..985aa862c 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gophercloud/gophercloud/v2 v2.9.0 h1:Y9OMrwKF9EDERcHFSOTpf/6XGoAI0yOxmsLmQki4LPM= -github.com/gophercloud/gophercloud/v2 v2.9.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= +github.com/gophercloud/gophercloud/v2 v2.10.0 h1:NRadC0aHNvy4iMoFXj5AFiPmut/Sj3hAPAo9B59VMGc= +github.com/gophercloud/gophercloud/v2 v2.10.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 h1:LS70kbNdqoalMwLXEzP9Xb/cYv9UCzWioXaOynxrytc= github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1/go.mod h1:qDhuzCRKi90/Yyl/yEqkg8+qABEvK44LhP0D3GWKGtY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= From d802ba713537efdbe3366761f14d01aaa9629c8d Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 7 Jan 2026 11:35:46 -0300 Subject: [PATCH 008/121] Update copyright date Happy new year :). --- README.md | 2 +- api/v1alpha1/zz_generated.deepcopy.go | 2 +- api/v1alpha1/zz_generated.domain-resource.go | 2 +- api/v1alpha1/zz_generated.flavor-resource.go | 2 +- api/v1alpha1/zz_generated.floatingip-resource.go | 2 +- api/v1alpha1/zz_generated.group-resource.go | 2 +- api/v1alpha1/zz_generated.image-resource.go | 2 +- api/v1alpha1/zz_generated.keypair-resource.go | 2 +- api/v1alpha1/zz_generated.network-resource.go | 2 +- api/v1alpha1/zz_generated.port-resource.go | 2 +- api/v1alpha1/zz_generated.project-resource.go | 2 +- api/v1alpha1/zz_generated.role-resource.go | 2 +- api/v1alpha1/zz_generated.router-resource.go | 2 +- api/v1alpha1/zz_generated.securitygroup-resource.go | 2 +- api/v1alpha1/zz_generated.server-resource.go | 2 +- api/v1alpha1/zz_generated.servergroup-resource.go | 2 +- api/v1alpha1/zz_generated.service-resource.go | 2 +- api/v1alpha1/zz_generated.subnet-resource.go | 2 +- api/v1alpha1/zz_generated.volume-resource.go | 2 +- api/v1alpha1/zz_generated.volumetype-resource.go | 2 +- cmd/models-schema/zz_generated.openapi.go | 2 +- cmd/resource-generator/data/adapter.template | 2 +- cmd/resource-generator/data/api.template | 2 +- cmd/resource-generator/data/controller.template | 2 +- .../data/internal-osclients-mock-doc.go.template | 2 +- cmd/resource-generator/main.go | 6 ------ cmd/scaffold-controller/data/api/types.go.template | 2 +- cmd/scaffold-controller/data/client/client.go.template | 2 +- .../data/controller/actuator.go.template | 2 +- .../data/controller/actuator_test.go.template | 2 +- .../data/controller/controller.go.template | 2 +- cmd/scaffold-controller/data/controller/status.go.template | 2 +- cmd/scaffold-controller/main.go | 3 --- hack/boilerplate.go.txt | 2 +- internal/controllers/domain/zz_generated.adapter.go | 2 +- internal/controllers/domain/zz_generated.controller.go | 2 +- internal/controllers/flavor/zz_generated.adapter.go | 2 +- internal/controllers/flavor/zz_generated.controller.go | 2 +- internal/controllers/floatingip/zz_generated.adapter.go | 2 +- internal/controllers/floatingip/zz_generated.controller.go | 2 +- internal/controllers/group/zz_generated.adapter.go | 2 +- internal/controllers/group/zz_generated.controller.go | 2 +- internal/controllers/image/zz_generated.adapter.go | 2 +- internal/controllers/image/zz_generated.controller.go | 2 +- internal/controllers/keypair/zz_generated.adapter.go | 2 +- internal/controllers/keypair/zz_generated.controller.go | 2 +- internal/controllers/network/zz_generated.adapter.go | 2 +- internal/controllers/network/zz_generated.controller.go | 2 +- internal/controllers/port/zz_generated.adapter.go | 2 +- internal/controllers/port/zz_generated.controller.go | 2 +- internal/controllers/project/zz_generated.adapter.go | 2 +- internal/controllers/project/zz_generated.controller.go | 2 +- internal/controllers/role/zz_generated.adapter.go | 2 +- internal/controllers/role/zz_generated.controller.go | 2 +- internal/controllers/router/zz_generated.adapter.go | 2 +- internal/controllers/router/zz_generated.controller.go | 2 +- internal/controllers/securitygroup/zz_generated.adapter.go | 2 +- .../controllers/securitygroup/zz_generated.controller.go | 2 +- internal/controllers/server/zz_generated.adapter.go | 2 +- internal/controllers/server/zz_generated.controller.go | 2 +- internal/controllers/servergroup/zz_generated.adapter.go | 2 +- internal/controllers/servergroup/zz_generated.controller.go | 2 +- internal/controllers/service/zz_generated.adapter.go | 2 +- internal/controllers/service/zz_generated.controller.go | 2 +- internal/controllers/subnet/zz_generated.adapter.go | 2 +- internal/controllers/subnet/zz_generated.controller.go | 2 +- internal/controllers/volume/zz_generated.adapter.go | 2 +- internal/controllers/volume/zz_generated.controller.go | 2 +- internal/controllers/volumetype/zz_generated.adapter.go | 2 +- internal/controllers/volumetype/zz_generated.controller.go | 2 +- internal/osclients/mock/compute.go | 2 +- internal/osclients/mock/doc.go | 2 +- internal/osclients/mock/domain.go | 2 +- internal/osclients/mock/group.go | 2 +- internal/osclients/mock/identity.go | 2 +- internal/osclients/mock/image.go | 2 +- internal/osclients/mock/keypair.go | 2 +- internal/osclients/mock/networking.go | 2 +- internal/osclients/mock/role.go | 2 +- internal/osclients/mock/service.go | 2 +- internal/osclients/mock/volume.go | 2 +- internal/osclients/mock/volumetype.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/address.go | 2 +- .../applyconfiguration/api/v1alpha1/allocationpool.go | 2 +- .../applyconfiguration/api/v1alpha1/allocationpoolstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/allowedaddresspair.go | 2 +- .../api/v1alpha1/allowedaddresspairstatus.go | 2 +- .../api/v1alpha1/cloudcredentialsreference.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/domain.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/domainfilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/domainimport.go | 2 +- .../applyconfiguration/api/v1alpha1/domainresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/domainresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/externalgateway.go | 2 +- .../api/v1alpha1/externalgatewaystatus.go | 2 +- .../applyconfiguration/api/v1alpha1/filterbykeystonetags.go | 2 +- .../applyconfiguration/api/v1alpha1/filterbyneutrontags.go | 2 +- .../applyconfiguration/api/v1alpha1/filterbyservertags.go | 2 +- .../applyconfiguration/api/v1alpha1/fixedipstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/flavor.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/flavorfilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/flavorimport.go | 2 +- .../applyconfiguration/api/v1alpha1/flavorresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/flavorresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/floatingip.go | 2 +- .../applyconfiguration/api/v1alpha1/floatingipfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/floatingipimport.go | 2 +- .../api/v1alpha1/floatingipresourcespec.go | 2 +- .../api/v1alpha1/floatingipresourcestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/floatingipspec.go | 2 +- .../applyconfiguration/api/v1alpha1/floatingipstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/group.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/groupfilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/groupimport.go | 2 +- .../applyconfiguration/api/v1alpha1/groupresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/groupresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/hostroute.go | 2 +- .../applyconfiguration/api/v1alpha1/hostroutestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/image.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/imagecontent.go | 2 +- .../api/v1alpha1/imagecontentsourcedownload.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/imagefilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/imagehash.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/imageimport.go | 2 +- .../applyconfiguration/api/v1alpha1/imageproperties.go | 2 +- .../api/v1alpha1/imagepropertieshardware.go | 2 +- .../api/v1alpha1/imagepropertiesoperatingsystem.go | 2 +- .../applyconfiguration/api/v1alpha1/imageresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/imageresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/imagestatusextra.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/ipv6options.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/keypair.go | 2 +- .../applyconfiguration/api/v1alpha1/keypairfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/keypairimport.go | 2 +- .../applyconfiguration/api/v1alpha1/keypairresourcespec.go | 2 +- .../api/v1alpha1/keypairresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go | 2 +- .../applyconfiguration/api/v1alpha1/keypairstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/managedoptions.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/network.go | 2 +- .../applyconfiguration/api/v1alpha1/networkfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/networkimport.go | 2 +- .../applyconfiguration/api/v1alpha1/networkresourcespec.go | 2 +- .../api/v1alpha1/networkresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go | 2 +- .../applyconfiguration/api/v1alpha1/networkstatus.go | 2 +- .../api/v1alpha1/neutronstatusmetadata.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/port.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/portimport.go | 2 +- .../applyconfiguration/api/v1alpha1/portrangespec.go | 2 +- .../applyconfiguration/api/v1alpha1/portrangestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/portresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/portresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/portspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/project.go | 2 +- .../applyconfiguration/api/v1alpha1/projectfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/projectimport.go | 2 +- .../applyconfiguration/api/v1alpha1/projectresourcespec.go | 2 +- .../api/v1alpha1/projectresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go | 2 +- .../applyconfiguration/api/v1alpha1/projectstatus.go | 2 +- .../api/v1alpha1/providerpropertiesstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/role.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go | 2 +- .../applyconfiguration/api/v1alpha1/roleresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/roleresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/router.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/routerfilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/routerimport.go | 2 +- .../applyconfiguration/api/v1alpha1/routerinterface.go | 2 +- .../applyconfiguration/api/v1alpha1/routerinterfacespec.go | 2 +- .../api/v1alpha1/routerinterfacestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/routerresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/routerresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/securitygroup.go | 2 +- .../applyconfiguration/api/v1alpha1/securitygroupfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/securitygroupimport.go | 2 +- .../api/v1alpha1/securitygroupresourcespec.go | 2 +- .../api/v1alpha1/securitygroupresourcestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/securitygrouprule.go | 2 +- .../api/v1alpha1/securitygrouprulestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/securitygroupspec.go | 2 +- .../applyconfiguration/api/v1alpha1/securitygroupstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/server.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/serverfilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/servergroup.go | 2 +- .../applyconfiguration/api/v1alpha1/servergroupfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/servergroupimport.go | 2 +- .../api/v1alpha1/servergroupresourcespec.go | 2 +- .../api/v1alpha1/servergroupresourcestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/servergrouprules.go | 2 +- .../api/v1alpha1/servergrouprulesstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/servergroupspec.go | 2 +- .../applyconfiguration/api/v1alpha1/servergroupstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/serverimport.go | 2 +- .../api/v1alpha1/serverinterfacefixedip.go | 2 +- .../api/v1alpha1/serverinterfacestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/serverportspec.go | 2 +- .../applyconfiguration/api/v1alpha1/serverresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/serverresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/servervolumespec.go | 2 +- .../applyconfiguration/api/v1alpha1/servervolumestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/service.go | 2 +- .../applyconfiguration/api/v1alpha1/servicefilter.go | 2 +- .../applyconfiguration/api/v1alpha1/serviceimport.go | 2 +- .../applyconfiguration/api/v1alpha1/serviceresourcespec.go | 2 +- .../api/v1alpha1/serviceresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go | 2 +- .../applyconfiguration/api/v1alpha1/servicestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/subnet.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/subnetfilter.go | 2 +- .../applyconfiguration/api/v1alpha1/subnetgateway.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/subnetimport.go | 2 +- .../applyconfiguration/api/v1alpha1/subnetresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/subnetresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/userdataspec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/volume.go | 2 +- .../api/v1alpha1/volumeattachmentstatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/volumefilter.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/volumeimport.go | 2 +- .../applyconfiguration/api/v1alpha1/volumemetadata.go | 2 +- .../applyconfiguration/api/v1alpha1/volumemetadatastatus.go | 2 +- .../applyconfiguration/api/v1alpha1/volumeresourcespec.go | 2 +- .../applyconfiguration/api/v1alpha1/volumeresourcestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go | 2 +- pkg/clients/applyconfiguration/api/v1alpha1/volumetype.go | 2 +- .../applyconfiguration/api/v1alpha1/volumetypeextraspec.go | 2 +- .../api/v1alpha1/volumetypeextraspecstatus.go | 2 +- .../applyconfiguration/api/v1alpha1/volumetypefilter.go | 2 +- .../applyconfiguration/api/v1alpha1/volumetypeimport.go | 2 +- .../api/v1alpha1/volumetyperesourcespec.go | 2 +- .../api/v1alpha1/volumetyperesourcestatus.go | 2 +- .../applyconfiguration/api/v1alpha1/volumetypespec.go | 2 +- .../applyconfiguration/api/v1alpha1/volumetypestatus.go | 2 +- pkg/clients/applyconfiguration/internal/internal.go | 2 +- pkg/clients/applyconfiguration/utils.go | 2 +- pkg/clients/clientset/clientset/clientset.go | 2 +- pkg/clients/clientset/clientset/fake/clientset_generated.go | 2 +- pkg/clients/clientset/clientset/fake/doc.go | 2 +- pkg/clients/clientset/clientset/fake/register.go | 2 +- pkg/clients/clientset/clientset/scheme/doc.go | 2 +- pkg/clients/clientset/clientset/scheme/register.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/api_client.go | 2 +- pkg/clients/clientset/clientset/typed/api/v1alpha1/doc.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/domain.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/fake/doc.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_api_client.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_domain.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_flavor.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_floatingip.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_group.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_image.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_keypair.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_network.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_port.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_project.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_role.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_router.go | 2 +- .../typed/api/v1alpha1/fake/fake_routerinterface.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_securitygroup.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_server.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_servergroup.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_service.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_subnet.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_volume.go | 2 +- .../clientset/typed/api/v1alpha1/fake/fake_volumetype.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/flavor.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/floatingip.go | 2 +- .../clientset/typed/api/v1alpha1/generated_expansion.go | 2 +- pkg/clients/clientset/clientset/typed/api/v1alpha1/group.go | 2 +- pkg/clients/clientset/clientset/typed/api/v1alpha1/image.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/keypair.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/network.go | 2 +- pkg/clients/clientset/clientset/typed/api/v1alpha1/port.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/project.go | 2 +- pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/router.go | 2 +- .../clientset/typed/api/v1alpha1/routerinterface.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/securitygroup.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/server.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/servergroup.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/service.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/subnet.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/volume.go | 2 +- .../clientset/clientset/typed/api/v1alpha1/volumetype.go | 2 +- pkg/clients/informers/externalversions/api/interface.go | 2 +- .../informers/externalversions/api/v1alpha1/domain.go | 2 +- .../informers/externalversions/api/v1alpha1/flavor.go | 2 +- .../informers/externalversions/api/v1alpha1/floatingip.go | 2 +- .../informers/externalversions/api/v1alpha1/group.go | 2 +- .../informers/externalversions/api/v1alpha1/image.go | 2 +- .../informers/externalversions/api/v1alpha1/interface.go | 2 +- .../informers/externalversions/api/v1alpha1/keypair.go | 2 +- .../informers/externalversions/api/v1alpha1/network.go | 2 +- pkg/clients/informers/externalversions/api/v1alpha1/port.go | 2 +- .../informers/externalversions/api/v1alpha1/project.go | 2 +- pkg/clients/informers/externalversions/api/v1alpha1/role.go | 2 +- .../informers/externalversions/api/v1alpha1/router.go | 2 +- .../externalversions/api/v1alpha1/routerinterface.go | 2 +- .../externalversions/api/v1alpha1/securitygroup.go | 2 +- .../informers/externalversions/api/v1alpha1/server.go | 2 +- .../informers/externalversions/api/v1alpha1/servergroup.go | 2 +- .../informers/externalversions/api/v1alpha1/service.go | 2 +- .../informers/externalversions/api/v1alpha1/subnet.go | 2 +- .../informers/externalversions/api/v1alpha1/volume.go | 2 +- .../informers/externalversions/api/v1alpha1/volumetype.go | 2 +- pkg/clients/informers/externalversions/factory.go | 2 +- pkg/clients/informers/externalversions/generic.go | 2 +- .../internalinterfaces/factory_interfaces.go | 2 +- pkg/clients/listers/api/v1alpha1/domain.go | 2 +- pkg/clients/listers/api/v1alpha1/expansion_generated.go | 2 +- pkg/clients/listers/api/v1alpha1/flavor.go | 2 +- pkg/clients/listers/api/v1alpha1/floatingip.go | 2 +- pkg/clients/listers/api/v1alpha1/group.go | 2 +- pkg/clients/listers/api/v1alpha1/image.go | 2 +- pkg/clients/listers/api/v1alpha1/keypair.go | 2 +- pkg/clients/listers/api/v1alpha1/network.go | 2 +- pkg/clients/listers/api/v1alpha1/port.go | 2 +- pkg/clients/listers/api/v1alpha1/project.go | 2 +- pkg/clients/listers/api/v1alpha1/role.go | 2 +- pkg/clients/listers/api/v1alpha1/router.go | 2 +- pkg/clients/listers/api/v1alpha1/routerinterface.go | 2 +- pkg/clients/listers/api/v1alpha1/securitygroup.go | 2 +- pkg/clients/listers/api/v1alpha1/server.go | 2 +- pkg/clients/listers/api/v1alpha1/servergroup.go | 2 +- pkg/clients/listers/api/v1alpha1/service.go | 2 +- pkg/clients/listers/api/v1alpha1/subnet.go | 2 +- pkg/clients/listers/api/v1alpha1/volume.go | 2 +- pkg/clients/listers/api/v1alpha1/volumetype.go | 2 +- 349 files changed, 347 insertions(+), 356 deletions(-) diff --git a/README.md b/README.md index 0eb8b6a23..17597fdbb 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ kubectl delete -f $ORC_RELEASE ## License -Copyright 2024. +Copyright 2026. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 093e63451..1b90b21c1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.domain-resource.go b/api/v1alpha1/zz_generated.domain-resource.go index 60b3357d6..9fd2b85a4 100644 --- a/api/v1alpha1/zz_generated.domain-resource.go +++ b/api/v1alpha1/zz_generated.domain-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.flavor-resource.go b/api/v1alpha1/zz_generated.flavor-resource.go index 6577cfb64..977264ed2 100644 --- a/api/v1alpha1/zz_generated.flavor-resource.go +++ b/api/v1alpha1/zz_generated.flavor-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.floatingip-resource.go b/api/v1alpha1/zz_generated.floatingip-resource.go index ec90dd026..66993c36e 100644 --- a/api/v1alpha1/zz_generated.floatingip-resource.go +++ b/api/v1alpha1/zz_generated.floatingip-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.group-resource.go b/api/v1alpha1/zz_generated.group-resource.go index c93a74b88..ffee5b120 100644 --- a/api/v1alpha1/zz_generated.group-resource.go +++ b/api/v1alpha1/zz_generated.group-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.image-resource.go b/api/v1alpha1/zz_generated.image-resource.go index 41e5785f1..dc5c80d4d 100644 --- a/api/v1alpha1/zz_generated.image-resource.go +++ b/api/v1alpha1/zz_generated.image-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.keypair-resource.go b/api/v1alpha1/zz_generated.keypair-resource.go index e0e39301a..77f31369a 100644 --- a/api/v1alpha1/zz_generated.keypair-resource.go +++ b/api/v1alpha1/zz_generated.keypair-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.network-resource.go b/api/v1alpha1/zz_generated.network-resource.go index bc5a017b1..6d3a89d85 100644 --- a/api/v1alpha1/zz_generated.network-resource.go +++ b/api/v1alpha1/zz_generated.network-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.port-resource.go b/api/v1alpha1/zz_generated.port-resource.go index 631ec707b..43f9ef7e0 100644 --- a/api/v1alpha1/zz_generated.port-resource.go +++ b/api/v1alpha1/zz_generated.port-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.project-resource.go b/api/v1alpha1/zz_generated.project-resource.go index ffd861f4f..0845855cb 100644 --- a/api/v1alpha1/zz_generated.project-resource.go +++ b/api/v1alpha1/zz_generated.project-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go index 6161c0421..20befafe8 100644 --- a/api/v1alpha1/zz_generated.role-resource.go +++ b/api/v1alpha1/zz_generated.role-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.router-resource.go b/api/v1alpha1/zz_generated.router-resource.go index 45ca8887c..bde8905b2 100644 --- a/api/v1alpha1/zz_generated.router-resource.go +++ b/api/v1alpha1/zz_generated.router-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.securitygroup-resource.go b/api/v1alpha1/zz_generated.securitygroup-resource.go index 33f221ec0..babddf920 100644 --- a/api/v1alpha1/zz_generated.securitygroup-resource.go +++ b/api/v1alpha1/zz_generated.securitygroup-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.server-resource.go b/api/v1alpha1/zz_generated.server-resource.go index 347cd2159..db28929e9 100644 --- a/api/v1alpha1/zz_generated.server-resource.go +++ b/api/v1alpha1/zz_generated.server-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.servergroup-resource.go b/api/v1alpha1/zz_generated.servergroup-resource.go index 6bc16a63a..ddc30d8bb 100644 --- a/api/v1alpha1/zz_generated.servergroup-resource.go +++ b/api/v1alpha1/zz_generated.servergroup-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.service-resource.go b/api/v1alpha1/zz_generated.service-resource.go index 93faf35ca..e4cbc2ecf 100644 --- a/api/v1alpha1/zz_generated.service-resource.go +++ b/api/v1alpha1/zz_generated.service-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.subnet-resource.go b/api/v1alpha1/zz_generated.subnet-resource.go index 072cbc307..fdff17f71 100644 --- a/api/v1alpha1/zz_generated.subnet-resource.go +++ b/api/v1alpha1/zz_generated.subnet-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.volume-resource.go b/api/v1alpha1/zz_generated.volume-resource.go index da525c450..ae830727c 100644 --- a/api/v1alpha1/zz_generated.volume-resource.go +++ b/api/v1alpha1/zz_generated.volume-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/api/v1alpha1/zz_generated.volumetype-resource.go b/api/v1alpha1/zz_generated.volumetype-resource.go index e2567a488..bc06ce713 100644 --- a/api/v1alpha1/zz_generated.volumetype-resource.go +++ b/api/v1alpha1/zz_generated.volumetype-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 37432d6b8..80dc9b6c6 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2025 The ORC Authors. +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. diff --git a/cmd/resource-generator/data/adapter.template b/cmd/resource-generator/data/adapter.template index 30e7ec827..7bec457ee 100644 --- a/cmd/resource-generator/data/adapter.template +++ b/cmd/resource-generator/data/adapter.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index 1abe2bf30..9b1655038 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/resource-generator/data/controller.template b/cmd/resource-generator/data/controller.template index 3879f901e..6a57c273d 100644 --- a/cmd/resource-generator/data/controller.template +++ b/cmd/resource-generator/data/controller.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/resource-generator/data/internal-osclients-mock-doc.go.template b/cmd/resource-generator/data/internal-osclients-mock-doc.go.template index 7ff63572c..ba9cfca3e 100644 --- a/cmd/resource-generator/data/internal-osclients-mock-doc.go.template +++ b/cmd/resource-generator/data/internal-osclients-mock-doc.go.template @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 6848b155f..b3f4ece76 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -12,7 +12,6 @@ import ( ) const ( - defaultYear = "2025" defaultAPIVersion = "v1alpha1" ) @@ -54,7 +53,6 @@ type additionalPrintColumn struct { type templateFields struct { APIVersion string - Year string Name string NameLower string IsNotNamed bool @@ -248,10 +246,6 @@ func addDefaults(resources []templateFields) { for i := range resources { resource := &resources[i] - if resource.Year == "" { - resource.Year = defaultYear - } - if resource.APIVersion == "" { resource.APIVersion = defaultAPIVersion } diff --git a/cmd/scaffold-controller/data/api/types.go.template b/cmd/scaffold-controller/data/api/types.go.template index b2cd8ee11..3d3c389b9 100644 --- a/cmd/scaffold-controller/data/api/types.go.template +++ b/cmd/scaffold-controller/data/api/types.go.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/scaffold-controller/data/client/client.go.template b/cmd/scaffold-controller/data/client/client.go.template index c0bebee4d..bba2af076 100644 --- a/cmd/scaffold-controller/data/client/client.go.template +++ b/cmd/scaffold-controller/data/client/client.go.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/scaffold-controller/data/controller/actuator.go.template b/cmd/scaffold-controller/data/controller/actuator.go.template index dc083e79d..4c7a6cc08 100644 --- a/cmd/scaffold-controller/data/controller/actuator.go.template +++ b/cmd/scaffold-controller/data/controller/actuator.go.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/scaffold-controller/data/controller/actuator_test.go.template b/cmd/scaffold-controller/data/controller/actuator_test.go.template index 2926ee701..59d7e9c31 100644 --- a/cmd/scaffold-controller/data/controller/actuator_test.go.template +++ b/cmd/scaffold-controller/data/controller/actuator_test.go.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/scaffold-controller/data/controller/controller.go.template b/cmd/scaffold-controller/data/controller/controller.go.template index 98d57af78..1fdc04434 100644 --- a/cmd/scaffold-controller/data/controller/controller.go.template +++ b/cmd/scaffold-controller/data/controller/controller.go.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/scaffold-controller/data/controller/status.go.template b/cmd/scaffold-controller/data/controller/status.go.template index ed9890263..98bc23b94 100644 --- a/cmd/scaffold-controller/data/controller/status.go.template +++ b/cmd/scaffold-controller/data/controller/status.go.template @@ -1,5 +1,5 @@ /* -Copyright {{ .Year }} The ORC Authors. +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. diff --git a/cmd/scaffold-controller/main.go b/cmd/scaffold-controller/main.go index 37ac50c75..03de800d8 100644 --- a/cmd/scaffold-controller/main.go +++ b/cmd/scaffold-controller/main.go @@ -13,7 +13,6 @@ import ( "regexp" "slices" "strings" - "time" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -32,7 +31,6 @@ type templateFields struct { OpenStackJSONObject string AvailablePollingPeriod int DeletingPollingPeriod int - Year int RequiredCreateDependencies strList OptionalCreateDependencies strList AllCreateDependencies strList @@ -180,7 +178,6 @@ func main() { fields.PackageName = strings.ToLower(fields.Kind) fields.GophercloudPackage = path.Base(fields.GophercloudModule) - fields.Year = time.Now().Year() fields.AllCreateDependencies = slices.Concat(fields.RequiredCreateDependencies, fields.OptionalCreateDependencies) render("data/api", filepath.Join("api", "v1alpha1"), &fields) diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 329a83718..2bdfd71ce 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/domain/zz_generated.adapter.go b/internal/controllers/domain/zz_generated.adapter.go index 89af22535..6a386af72 100644 --- a/internal/controllers/domain/zz_generated.adapter.go +++ b/internal/controllers/domain/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/domain/zz_generated.controller.go b/internal/controllers/domain/zz_generated.controller.go index 31fd025a2..42194e684 100644 --- a/internal/controllers/domain/zz_generated.controller.go +++ b/internal/controllers/domain/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/flavor/zz_generated.adapter.go b/internal/controllers/flavor/zz_generated.adapter.go index c82b74162..936fc6735 100644 --- a/internal/controllers/flavor/zz_generated.adapter.go +++ b/internal/controllers/flavor/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/flavor/zz_generated.controller.go b/internal/controllers/flavor/zz_generated.controller.go index 38cf8b85c..675989cc0 100644 --- a/internal/controllers/flavor/zz_generated.controller.go +++ b/internal/controllers/flavor/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/floatingip/zz_generated.adapter.go b/internal/controllers/floatingip/zz_generated.adapter.go index 2c4796f71..c0ff372fb 100644 --- a/internal/controllers/floatingip/zz_generated.adapter.go +++ b/internal/controllers/floatingip/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/floatingip/zz_generated.controller.go b/internal/controllers/floatingip/zz_generated.controller.go index 223181f4c..9d3324464 100644 --- a/internal/controllers/floatingip/zz_generated.controller.go +++ b/internal/controllers/floatingip/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/group/zz_generated.adapter.go b/internal/controllers/group/zz_generated.adapter.go index 48d281caf..be06e584f 100644 --- a/internal/controllers/group/zz_generated.adapter.go +++ b/internal/controllers/group/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/group/zz_generated.controller.go b/internal/controllers/group/zz_generated.controller.go index 572c0f289..39e06261a 100644 --- a/internal/controllers/group/zz_generated.controller.go +++ b/internal/controllers/group/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/image/zz_generated.adapter.go b/internal/controllers/image/zz_generated.adapter.go index c81752969..fe096571b 100644 --- a/internal/controllers/image/zz_generated.adapter.go +++ b/internal/controllers/image/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/image/zz_generated.controller.go b/internal/controllers/image/zz_generated.controller.go index 73bfa7160..a980a5c4f 100644 --- a/internal/controllers/image/zz_generated.controller.go +++ b/internal/controllers/image/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/keypair/zz_generated.adapter.go b/internal/controllers/keypair/zz_generated.adapter.go index 9b71b893a..d3b72644c 100644 --- a/internal/controllers/keypair/zz_generated.adapter.go +++ b/internal/controllers/keypair/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/keypair/zz_generated.controller.go b/internal/controllers/keypair/zz_generated.controller.go index 95bb9f371..c3bdcda17 100644 --- a/internal/controllers/keypair/zz_generated.controller.go +++ b/internal/controllers/keypair/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/network/zz_generated.adapter.go b/internal/controllers/network/zz_generated.adapter.go index 647cec323..771518735 100644 --- a/internal/controllers/network/zz_generated.adapter.go +++ b/internal/controllers/network/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/network/zz_generated.controller.go b/internal/controllers/network/zz_generated.controller.go index e2b23d0d9..f054559f0 100644 --- a/internal/controllers/network/zz_generated.controller.go +++ b/internal/controllers/network/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/port/zz_generated.adapter.go b/internal/controllers/port/zz_generated.adapter.go index e9ca55c79..1cafbd343 100644 --- a/internal/controllers/port/zz_generated.adapter.go +++ b/internal/controllers/port/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/port/zz_generated.controller.go b/internal/controllers/port/zz_generated.controller.go index 290986682..160f732dc 100644 --- a/internal/controllers/port/zz_generated.controller.go +++ b/internal/controllers/port/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/project/zz_generated.adapter.go b/internal/controllers/project/zz_generated.adapter.go index 0d6afee56..fea8a21c1 100644 --- a/internal/controllers/project/zz_generated.adapter.go +++ b/internal/controllers/project/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/project/zz_generated.controller.go b/internal/controllers/project/zz_generated.controller.go index 34984c633..7660eb2bb 100644 --- a/internal/controllers/project/zz_generated.controller.go +++ b/internal/controllers/project/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/role/zz_generated.adapter.go b/internal/controllers/role/zz_generated.adapter.go index 3c98f6eca..5587b85d4 100644 --- a/internal/controllers/role/zz_generated.adapter.go +++ b/internal/controllers/role/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/role/zz_generated.controller.go b/internal/controllers/role/zz_generated.controller.go index e3caa1f35..bc7cce067 100644 --- a/internal/controllers/role/zz_generated.controller.go +++ b/internal/controllers/role/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/router/zz_generated.adapter.go b/internal/controllers/router/zz_generated.adapter.go index 27f6b7339..ccab08587 100644 --- a/internal/controllers/router/zz_generated.adapter.go +++ b/internal/controllers/router/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/router/zz_generated.controller.go b/internal/controllers/router/zz_generated.controller.go index 255858fc5..71e247334 100644 --- a/internal/controllers/router/zz_generated.controller.go +++ b/internal/controllers/router/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/securitygroup/zz_generated.adapter.go b/internal/controllers/securitygroup/zz_generated.adapter.go index eb98fad70..1b055740c 100644 --- a/internal/controllers/securitygroup/zz_generated.adapter.go +++ b/internal/controllers/securitygroup/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/securitygroup/zz_generated.controller.go b/internal/controllers/securitygroup/zz_generated.controller.go index d6a0449a7..e3477ad53 100644 --- a/internal/controllers/securitygroup/zz_generated.controller.go +++ b/internal/controllers/securitygroup/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/server/zz_generated.adapter.go b/internal/controllers/server/zz_generated.adapter.go index 340fff439..1b51cde39 100644 --- a/internal/controllers/server/zz_generated.adapter.go +++ b/internal/controllers/server/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/server/zz_generated.controller.go b/internal/controllers/server/zz_generated.controller.go index 6a5156eb0..d3aee5648 100644 --- a/internal/controllers/server/zz_generated.controller.go +++ b/internal/controllers/server/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/servergroup/zz_generated.adapter.go b/internal/controllers/servergroup/zz_generated.adapter.go index ee366d633..dc272f462 100644 --- a/internal/controllers/servergroup/zz_generated.adapter.go +++ b/internal/controllers/servergroup/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/servergroup/zz_generated.controller.go b/internal/controllers/servergroup/zz_generated.controller.go index 8da2d169d..181f872cc 100644 --- a/internal/controllers/servergroup/zz_generated.controller.go +++ b/internal/controllers/servergroup/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/service/zz_generated.adapter.go b/internal/controllers/service/zz_generated.adapter.go index 3f8d585bc..f70ba04d9 100644 --- a/internal/controllers/service/zz_generated.adapter.go +++ b/internal/controllers/service/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/service/zz_generated.controller.go b/internal/controllers/service/zz_generated.controller.go index a1fe4a121..0e0232fae 100644 --- a/internal/controllers/service/zz_generated.controller.go +++ b/internal/controllers/service/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/subnet/zz_generated.adapter.go b/internal/controllers/subnet/zz_generated.adapter.go index 102a41b91..34c84d5b8 100644 --- a/internal/controllers/subnet/zz_generated.adapter.go +++ b/internal/controllers/subnet/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/subnet/zz_generated.controller.go b/internal/controllers/subnet/zz_generated.controller.go index 58e27bae3..73fe60115 100644 --- a/internal/controllers/subnet/zz_generated.controller.go +++ b/internal/controllers/subnet/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/volume/zz_generated.adapter.go b/internal/controllers/volume/zz_generated.adapter.go index fe9aebdd1..956b64693 100644 --- a/internal/controllers/volume/zz_generated.adapter.go +++ b/internal/controllers/volume/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/volume/zz_generated.controller.go b/internal/controllers/volume/zz_generated.controller.go index d4585cef1..080ff160c 100644 --- a/internal/controllers/volume/zz_generated.controller.go +++ b/internal/controllers/volume/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/volumetype/zz_generated.adapter.go b/internal/controllers/volumetype/zz_generated.adapter.go index 2490ef70e..9f19fa751 100644 --- a/internal/controllers/volumetype/zz_generated.adapter.go +++ b/internal/controllers/volumetype/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/volumetype/zz_generated.controller.go b/internal/controllers/volumetype/zz_generated.controller.go index 0e551e2f2..74d96c985 100644 --- a/internal/controllers/volumetype/zz_generated.controller.go +++ b/internal/controllers/volumetype/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/compute.go b/internal/osclients/mock/compute.go index c22ab6984..bb85f073c 100644 --- a/internal/osclients/mock/compute.go +++ b/internal/osclients/mock/compute.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 47292b65f..0da3b97ba 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/domain.go b/internal/osclients/mock/domain.go index bfc9c6c1c..a16b2b178 100644 --- a/internal/osclients/mock/domain.go +++ b/internal/osclients/mock/domain.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/group.go b/internal/osclients/mock/group.go index f4c5da425..0612ebed2 100644 --- a/internal/osclients/mock/group.go +++ b/internal/osclients/mock/group.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/identity.go b/internal/osclients/mock/identity.go index 70079f962..17f8e4f6c 100644 --- a/internal/osclients/mock/identity.go +++ b/internal/osclients/mock/identity.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/image.go b/internal/osclients/mock/image.go index dc5dba62e..939917569 100644 --- a/internal/osclients/mock/image.go +++ b/internal/osclients/mock/image.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/keypair.go b/internal/osclients/mock/keypair.go index e4dfac055..f5d28c603 100644 --- a/internal/osclients/mock/keypair.go +++ b/internal/osclients/mock/keypair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/networking.go b/internal/osclients/mock/networking.go index 9b5e25046..ceab233d2 100644 --- a/internal/osclients/mock/networking.go +++ b/internal/osclients/mock/networking.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/role.go b/internal/osclients/mock/role.go index 3108304d2..08ea8397c 100644 --- a/internal/osclients/mock/role.go +++ b/internal/osclients/mock/role.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/service.go b/internal/osclients/mock/service.go index b8c9191a8..05bee911b 100644 --- a/internal/osclients/mock/service.go +++ b/internal/osclients/mock/service.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/volume.go b/internal/osclients/mock/volume.go index efb1390f0..ca736d3d1 100644 --- a/internal/osclients/mock/volume.go +++ b/internal/osclients/mock/volume.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/osclients/mock/volumetype.go b/internal/osclients/mock/volumetype.go index e0dd5be49..08a648872 100644 --- a/internal/osclients/mock/volumetype.go +++ b/internal/osclients/mock/volumetype.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/address.go b/pkg/clients/applyconfiguration/api/v1alpha1/address.go index 74478a9e0..7db825b3b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/address.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/address.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/allocationpool.go b/pkg/clients/applyconfiguration/api/v1alpha1/allocationpool.go index d152ed380..bf3667ad7 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/allocationpool.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/allocationpool.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/allocationpoolstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/allocationpoolstatus.go index a806531d6..a66c82e05 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/allocationpoolstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/allocationpoolstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspair.go b/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspair.go index 48d77abbe..b80e96c5d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspair.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspairstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspairstatus.go index d18d0a5d8..19ec2e80f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspairstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/allowedaddresspairstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/cloudcredentialsreference.go b/pkg/clients/applyconfiguration/api/v1alpha1/cloudcredentialsreference.go index 455c0eb1e..d619ae9ff 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/cloudcredentialsreference.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/cloudcredentialsreference.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domain.go b/pkg/clients/applyconfiguration/api/v1alpha1/domain.go index 5f1b4216b..b8748feeb 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domain.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domain.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainfilter.go index bed3c4ef7..49152b6a3 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainimport.go index 26198ccab..a208643cd 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcespec.go index c18282d4f..1b9a0ea6b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcestatus.go index ca5d524f5..91911434a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go index 2c72fdef4..e5357a87e 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go index 0294b7a06..c7540a168 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/externalgateway.go b/pkg/clients/applyconfiguration/api/v1alpha1/externalgateway.go index d16d8f6b5..304964005 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/externalgateway.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/externalgateway.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/externalgatewaystatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/externalgatewaystatus.go index a93eaaab1..3b07be9fd 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/externalgatewaystatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/externalgatewaystatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/filterbykeystonetags.go b/pkg/clients/applyconfiguration/api/v1alpha1/filterbykeystonetags.go index bc4fe7536..925726e12 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/filterbykeystonetags.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/filterbykeystonetags.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/filterbyneutrontags.go b/pkg/clients/applyconfiguration/api/v1alpha1/filterbyneutrontags.go index 785486a6f..c8796fafd 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/filterbyneutrontags.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/filterbyneutrontags.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/filterbyservertags.go b/pkg/clients/applyconfiguration/api/v1alpha1/filterbyservertags.go index e8e8b6347..a7360cde9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/filterbyservertags.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/filterbyservertags.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/fixedipstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/fixedipstatus.go index f12c972b2..88afdedf4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/fixedipstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/fixedipstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavor.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavor.go index b6c7a5a4c..da6e101b2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavor.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavor.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorfilter.go index 90864da00..84ea15f96 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorimport.go index 8c9f5931b..a8e657fc0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go index 6f3321810..335f722a6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go index 28ab52d58..7b4996ed4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go index f60351b00..abe7c0d07 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go index 660532d5d..928a60e26 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingip.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingip.go index 29c0ddd81..2923fc5df 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingip.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingip.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipfilter.go index 518de3eb0..bdaf24e7a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipimport.go index 261d3b3d7..759a0a4b3 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcespec.go index 11b4cb473..7de2cc0ef 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcestatus.go index c3c792fb2..697f5ba19 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go index 3a34d0527..8fe12ac3a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go index c78821748..61291bdd7 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/group.go b/pkg/clients/applyconfiguration/api/v1alpha1/group.go index c01e53c37..1904aa4f2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/group.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/group.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupfilter.go index 74e576e8f..974125061 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupimport.go index 8b2722827..f5ea5aaff 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcespec.go index 1be73b8ad..3d914a439 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcestatus.go index 53f2fd0ab..bfe2aa55f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go index 0aaee9e04..59a744101 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go index 88af39d51..564e9cdc4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostroute.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostroute.go index 19be79bac..4cc09b094 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/hostroute.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostroute.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostroutestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostroutestatus.go index 90bfffa3d..11bf6a596 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/hostroutestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostroutestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/image.go b/pkg/clients/applyconfiguration/api/v1alpha1/image.go index 9a3d2d392..386817976 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/image.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/image.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagecontent.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagecontent.go index 6b096f15a..d9e68e2e6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagecontent.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagecontent.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagecontentsourcedownload.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagecontentsourcedownload.go index 9ed05d174..b00f1c48d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagecontentsourcedownload.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagecontentsourcedownload.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagefilter.go index 3d6a9bb2b..827d8c797 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagefilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagefilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagehash.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagehash.go index 5c8558384..67a0478d3 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagehash.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagehash.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imageimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/imageimport.go index bcf5fa561..27df0f019 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imageimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imageimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imageproperties.go b/pkg/clients/applyconfiguration/api/v1alpha1/imageproperties.go index 36265de38..f73ac4121 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imageproperties.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imageproperties.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertieshardware.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertieshardware.go index 926a0c247..2d7f89208 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertieshardware.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertieshardware.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertiesoperatingsystem.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertiesoperatingsystem.go index fa1789a4d..81f6858de 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertiesoperatingsystem.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagepropertiesoperatingsystem.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcespec.go index aff932b2b..5c1adb6ad 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcestatus.go index bc5c5db70..e697b64d0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imageresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go index ba1ea2252..7982dcda4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go index 15c7410b9..033e2cd30 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagestatusextra.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagestatusextra.go index e5a147d2b..d2fc95947 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagestatusextra.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagestatusextra.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/ipv6options.go b/pkg/clients/applyconfiguration/api/v1alpha1/ipv6options.go index a82b36222..d95b64185 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/ipv6options.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/ipv6options.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypair.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypair.go index db29ba7ba..9a5937b2b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypair.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairfilter.go index 22f0353bf..bee0e363b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairimport.go index ed766367b..14a0205b9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcespec.go index 1ae0c8fe9..fc2069cfa 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcestatus.go index 2be862ba2..f0047e4c1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go index ffe67fe9c..725c5278d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go index 5a4a51b13..fb316a629 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/managedoptions.go b/pkg/clients/applyconfiguration/api/v1alpha1/managedoptions.go index 89f690a9c..092ab7883 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/managedoptions.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/managedoptions.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/network.go b/pkg/clients/applyconfiguration/api/v1alpha1/network.go index f8bac102e..76876d253 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/network.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/network.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkfilter.go index f1a1d5f87..557babf8b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkimport.go index fa9f7702e..f0e6311cf 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcespec.go index 8646ab92f..a85d19867 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcestatus.go index 1935b223b..fa1d0a2e5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go index 8de986799..a27a7b21c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go index 1c8312a59..1d671bd04 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/neutronstatusmetadata.go b/pkg/clients/applyconfiguration/api/v1alpha1/neutronstatusmetadata.go index b9cc4fd26..04d7aca93 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/neutronstatusmetadata.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/neutronstatusmetadata.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/port.go b/pkg/clients/applyconfiguration/api/v1alpha1/port.go index 2ef3698b9..17b2763e5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/port.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/port.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go index ab6d6e18d..2db6c09a6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/portimport.go index 272da1940..fd760ff61 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go index 811b56f0e..64bc4a298 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portrangespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go index 4728e5ca0..b92bd01c6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portrangestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index ac7d4ed07..d8b5222e8 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go index 1fd734822..847c4d61f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go index 6b6a549f2..f3a31f9d1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go index 700231629..f902f1538 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/project.go b/pkg/clients/applyconfiguration/api/v1alpha1/project.go index 7bb0dc9a6..4ce5d897a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/project.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/project.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go index 0bdcf6a82..90b315787 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectimport.go index 395deb31f..486e72fd5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go index fc8721b8c..8a5ef4250 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go index 5520405c9..0e2ad1f94 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go index d98c9dc9c..fe81e80ba 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go index f829757ae..328980bcf 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/providerpropertiesstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/providerpropertiesstatus.go index a274c3f3b..47bfc8c2a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/providerpropertiesstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/providerpropertiesstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/role.go b/pkg/clients/applyconfiguration/api/v1alpha1/role.go index 14fcda794..1fcf9bf44 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/role.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/role.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go index d27c0d297..2194e2982 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolefilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go index da25d500b..336bc2fe1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go index 4f3fad0c5..c1288f979 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go index 847f97c26..b915ba35a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go index 92fa50276..05205d08b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go index cc11f42f4..8731d9e8b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/router.go b/pkg/clients/applyconfiguration/api/v1alpha1/router.go index 3fe5add44..52d6a7d07 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/router.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/router.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerfilter.go index d87275dfc..100978fb6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerimport.go index c48eae55e..d182caade 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterface.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterface.go index 671f33942..caf20d3d0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterface.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go index 1af36e749..4fccc28c6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go index 8e162f3bf..280668da5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcespec.go index 2b4cb81b2..4b0a09ff7 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcestatus.go index b89a23529..985da1065 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go index cf70e4771..fb3da1400 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go index ca1eca811..2b42ab43e 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroup.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroup.go index 56ccc6e15..2f05e98c2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroup.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupfilter.go index e4368095d..f11d7bbdc 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupimport.go index 4faa3f3dd..20855e5a3 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcespec.go index 17d57aafc..f02908f6a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcestatus.go index 2ccdb930a..2709c209b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go index f6513d5f0..09fa8329e 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprule.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go index 8bd924f2a..7d9b31ba6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygrouprulestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go index cb624b694..aea02dbb7 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go index dae3cc7ac..8ff720652 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/server.go b/pkg/clients/applyconfiguration/api/v1alpha1/server.go index 60db77744..21adde247 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/server.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/server.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverfilter.go index 822ba54d3..1212b6a29 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroup.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroup.go index b5bbc8ba0..e534de68c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroup.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupfilter.go index 0576765dd..70d12d4fc 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupimport.go index c46e8a88a..0aa17ce03 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcespec.go index 0b9f058e7..08ef8c08a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcestatus.go index 67b1d3be7..ddf1524b2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprules.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprules.go index e6dc75cef..ab7af2939 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprules.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprules.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprulesstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprulesstatus.go index add47f563..cb61dbfb3 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprulesstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergrouprulesstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go index 096f6f610..21efdd462 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go index b9b392b12..8b7e8ce34 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverimport.go index c93fa9f7d..30ef85c81 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacefixedip.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacefixedip.go index 1bbab2048..ce743b6d0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacefixedip.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacefixedip.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacestatus.go index 609d5664a..add66fb42 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverinterfacestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverportspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverportspec.go index a09007518..3b812beae 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverportspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverportspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go index 5233713da..bc0e6e435 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go index 119583f20..7d2c173cc 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go index 03baf01b1..2e284079c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go index d27cd78a9..c433aafb2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servervolumespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/servervolumespec.go index 9ca2d0c40..bccea241b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servervolumespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servervolumespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servervolumestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servervolumestatus.go index 15d6b7e4b..601fcf8f9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servervolumestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servervolumestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/service.go b/pkg/clients/applyconfiguration/api/v1alpha1/service.go index 460eeb720..30619a81b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/service.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/service.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servicefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/servicefilter.go index 1ca8d84b3..284623ca4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servicefilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servicefilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serviceimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/serviceimport.go index b719d046d..42ccae04a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serviceimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serviceimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcespec.go index f03f3c54b..5fd35cef6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcestatus.go index 88f1a8fc1..1ee6b1169 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serviceresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go index 03cafe9b5..1cc5b6645 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go index 80bf199ba..89fa866d4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnet.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnet.go index 0c0f127fc..b4d653764 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnet.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnet.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetfilter.go index 7eac6055a..77070b0b1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetgateway.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetgateway.go index e82635429..8982b39d4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetgateway.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetgateway.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetimport.go index 7b8255fef..483fe0f1e 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcespec.go index 1e0e65238..73f73099c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcestatus.go index 8c0f8f918..7da523a8d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go index ff3c3c27c..4c092dd01 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go index 8b8b1d216..e538e3724 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userdataspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userdataspec.go index bbfc17368..394503c52 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userdataspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userdataspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volume.go b/pkg/clients/applyconfiguration/api/v1alpha1/volume.go index 648631092..5dc4ae9ce 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volume.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volume.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeattachmentstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeattachmentstatus.go index d005148bf..bf2f8469c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeattachmentstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeattachmentstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumefilter.go index cd69f5931..5f501b8f9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumefilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumefilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeimport.go index 2c8a4f667..0607f42e6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadata.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadata.go index 243503205..d8a099f3a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadata.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadata.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadatastatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadatastatus.go index 680b4a38c..5791a90fd 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadatastatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumemetadatastatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go index 7386c2714..efa5c19ca 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go index d3113778e..1ac93544c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go index 1444e10c1..e0ffdcbca 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go index 6d93bb445..3e6be3743 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetype.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetype.go index baa70c6d4..67d365b19 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetype.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetype.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspec.go index 4dde7e431..bebc95ca9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspecstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspecstatus.go index 3bec3d64e..17928fc31 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspecstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeextraspecstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypefilter.go index 252352714..3173d031b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypefilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypefilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeimport.go index d6756b106..e228e4c2f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypeimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcespec.go index f85aef90c..88691bffb 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcestatus.go index 9550c19b5..a6e393435 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetyperesourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go index ee9cb26d7..d4540dcfd 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go index 78e67a846..64a797263 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index e3cbf8592..6a989754e 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index e3166fefe..478b73a00 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/clientset.go b/pkg/clients/clientset/clientset/clientset.go index 5dc724996..a0302397c 100644 --- a/pkg/clients/clientset/clientset/clientset.go +++ b/pkg/clients/clientset/clientset/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/fake/clientset_generated.go b/pkg/clients/clientset/clientset/fake/clientset_generated.go index 39bf8513e..042753987 100644 --- a/pkg/clients/clientset/clientset/fake/clientset_generated.go +++ b/pkg/clients/clientset/clientset/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/fake/doc.go b/pkg/clients/clientset/clientset/fake/doc.go index 089f28395..5b176fb04 100644 --- a/pkg/clients/clientset/clientset/fake/doc.go +++ b/pkg/clients/clientset/clientset/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/fake/register.go b/pkg/clients/clientset/clientset/fake/register.go index 69e224215..3c6caad14 100644 --- a/pkg/clients/clientset/clientset/fake/register.go +++ b/pkg/clients/clientset/clientset/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/scheme/doc.go b/pkg/clients/clientset/clientset/scheme/doc.go index 959b8b28d..955d36bd2 100644 --- a/pkg/clients/clientset/clientset/scheme/doc.go +++ b/pkg/clients/clientset/clientset/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/scheme/register.go b/pkg/clients/clientset/clientset/scheme/register.go index 37f2ae0a4..333c2c424 100644 --- a/pkg/clients/clientset/clientset/scheme/register.go +++ b/pkg/clients/clientset/clientset/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go index 4d2f93b0d..4317c8aa6 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/doc.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/doc.go index b757132de..ab5d0be93 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/doc.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/domain.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/domain.go index f76ed31fe..83ba6d973 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/domain.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/domain.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/doc.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/doc.go index 1b3dfc5a5..d409d454c 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/doc.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go index 44feeb45c..595446f05 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 @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_domain.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_domain.go index ef082eb77..ea78b8a40 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_domain.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_domain.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_flavor.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_flavor.go index 1686abb94..bf4efd93d 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_flavor.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_flavor.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_floatingip.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_floatingip.go index e720499d5..136c49056 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_floatingip.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_floatingip.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_group.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_group.go index c3a168e1c..2ef581ba1 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_group.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_group.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_image.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_image.go index 6efdd68ea..bc5bbc879 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_image.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_image.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_keypair.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_keypair.go index cbbf5a3a2..37553ac03 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_keypair.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_keypair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_network.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_network.go index 52d96bb68..f68d65923 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_network.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_network.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_port.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_port.go index 3f20c5d01..540a5ba2c 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_port.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_port.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_project.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_project.go index c1f1f6046..c75981692 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_project.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_project.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go index e7701df25..3dc54b77d 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_role.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_router.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_router.go index 831c9220e..dec48bdd2 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_router.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_router.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_routerinterface.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_routerinterface.go index 34d76d5cd..b519e73e8 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_routerinterface.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_routerinterface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_securitygroup.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_securitygroup.go index 2ef4b1ba6..865fb786c 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_securitygroup.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_securitygroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_server.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_server.go index 9ae492970..a8e37ad42 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_server.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_server.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_servergroup.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_servergroup.go index 61816c71a..3c1cbf2fb 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_servergroup.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_servergroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_service.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_service.go index 0c175dad5..17971c02b 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_service.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_service.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnet.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnet.go index 540939764..9dd38a30f 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnet.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_subnet.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volume.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volume.go index c1097298c..fef4e1e78 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volume.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volume.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volumetype.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volumetype.go index 7797551bd..126aebd95 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volumetype.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_volumetype.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/flavor.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/flavor.go index d2bcfddad..7f59e0ef1 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/flavor.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/flavor.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/floatingip.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/floatingip.go index defa84705..00a9802a4 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/floatingip.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/floatingip.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go index 56550a99f..558e399d0 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/group.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/group.go index 5dc034c44..a8e168094 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/group.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/group.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/image.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/image.go index d15056837..bb719a623 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/image.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/image.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/keypair.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/keypair.go index 71d9d50b6..f2d1f177e 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/keypair.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/keypair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/network.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/network.go index bd0756eb5..2422c0b25 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/network.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/network.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/port.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/port.go index 33fd81223..e59bc653b 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/port.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/port.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/project.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/project.go index 5dfd39fd6..e777bff87 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/project.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/project.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go index 779708067..5ac1433f6 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/role.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/router.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/router.go index 0e092644d..5da2176b0 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/router.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/router.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/routerinterface.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/routerinterface.go index 195f91f02..3d6e86c32 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/routerinterface.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/routerinterface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/securitygroup.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/securitygroup.go index c71c2affe..d996525f2 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/securitygroup.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/securitygroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/server.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/server.go index 0fba20892..77e068970 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/server.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/server.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/servergroup.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/servergroup.go index 4966a6301..71d968283 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/servergroup.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/servergroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/service.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/service.go index 1fc5509ea..ed1f4ddca 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/service.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/service.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnet.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnet.go index 10bebdfc7..8ef009c1f 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnet.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/subnet.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/volume.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/volume.go index 966563255..7c5147c1f 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/volume.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/volume.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/volumetype.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/volumetype.go index 352e3c31e..8aa0602c7 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/volumetype.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/volumetype.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/interface.go b/pkg/clients/informers/externalversions/api/interface.go index 796462031..ca024a2e7 100644 --- a/pkg/clients/informers/externalversions/api/interface.go +++ b/pkg/clients/informers/externalversions/api/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/domain.go b/pkg/clients/informers/externalversions/api/v1alpha1/domain.go index 4c5fba2de..2e0a8879a 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/domain.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/domain.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/flavor.go b/pkg/clients/informers/externalversions/api/v1alpha1/flavor.go index 3a941fc22..4f371b017 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/flavor.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/flavor.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/floatingip.go b/pkg/clients/informers/externalversions/api/v1alpha1/floatingip.go index 65cf18f1c..099d674b5 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/floatingip.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/floatingip.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/group.go b/pkg/clients/informers/externalversions/api/v1alpha1/group.go index c240e2021..eefe73c64 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/group.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/group.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/image.go b/pkg/clients/informers/externalversions/api/v1alpha1/image.go index c0a97354c..707dcfacd 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/image.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/image.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 1b4497815..2e06781ab 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/keypair.go b/pkg/clients/informers/externalversions/api/v1alpha1/keypair.go index f0c1e2b57..33ef79fd4 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/keypair.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/keypair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/network.go b/pkg/clients/informers/externalversions/api/v1alpha1/network.go index 24e300230..84e12259c 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/network.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/network.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/port.go b/pkg/clients/informers/externalversions/api/v1alpha1/port.go index 8eeaa5406..7e037c84d 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/port.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/port.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/project.go b/pkg/clients/informers/externalversions/api/v1alpha1/project.go index 345ac1c19..0025d8e21 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/project.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/project.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/role.go b/pkg/clients/informers/externalversions/api/v1alpha1/role.go index cfdb7de7a..7975f8f7e 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/role.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/role.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/router.go b/pkg/clients/informers/externalversions/api/v1alpha1/router.go index 8e5d3d4a2..fc472b5bd 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/router.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/router.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/routerinterface.go b/pkg/clients/informers/externalversions/api/v1alpha1/routerinterface.go index 1f94db3ab..b937e51f7 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/routerinterface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/routerinterface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/securitygroup.go b/pkg/clients/informers/externalversions/api/v1alpha1/securitygroup.go index 7680e921a..385356ccc 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/securitygroup.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/securitygroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/server.go b/pkg/clients/informers/externalversions/api/v1alpha1/server.go index 416e0ee84..130029213 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/server.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/server.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/servergroup.go b/pkg/clients/informers/externalversions/api/v1alpha1/servergroup.go index 70c6c1e33..3a3fd71c3 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/servergroup.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/servergroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/service.go b/pkg/clients/informers/externalversions/api/v1alpha1/service.go index dd9f14d78..43ca18e0a 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/service.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/service.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/subnet.go b/pkg/clients/informers/externalversions/api/v1alpha1/subnet.go index 4bfc120f0..6a902f657 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/subnet.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/subnet.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/volume.go b/pkg/clients/informers/externalversions/api/v1alpha1/volume.go index fb175347a..c42cc57a0 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/volume.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/volume.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/volumetype.go b/pkg/clients/informers/externalversions/api/v1alpha1/volumetype.go index be022c62a..34b6336eb 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/volumetype.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/volumetype.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/factory.go b/pkg/clients/informers/externalversions/factory.go index 3f0620df3..2260c31fb 100644 --- a/pkg/clients/informers/externalversions/factory.go +++ b/pkg/clients/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 30911d11f..1bb2313e0 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/clients/informers/externalversions/internalinterfaces/factory_interfaces.go index 1ff80aa05..38e582cf3 100644 --- a/pkg/clients/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/clients/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/domain.go b/pkg/clients/listers/api/v1alpha1/domain.go index 4cae34fba..1e3d17051 100644 --- a/pkg/clients/listers/api/v1alpha1/domain.go +++ b/pkg/clients/listers/api/v1alpha1/domain.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index ba2888731..1380d0372 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/flavor.go b/pkg/clients/listers/api/v1alpha1/flavor.go index 0f8cb4282..820fe098a 100644 --- a/pkg/clients/listers/api/v1alpha1/flavor.go +++ b/pkg/clients/listers/api/v1alpha1/flavor.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/floatingip.go b/pkg/clients/listers/api/v1alpha1/floatingip.go index f41a20321..f7dbc95c5 100644 --- a/pkg/clients/listers/api/v1alpha1/floatingip.go +++ b/pkg/clients/listers/api/v1alpha1/floatingip.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/group.go b/pkg/clients/listers/api/v1alpha1/group.go index 33ea8f0f5..1364f5214 100644 --- a/pkg/clients/listers/api/v1alpha1/group.go +++ b/pkg/clients/listers/api/v1alpha1/group.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/image.go b/pkg/clients/listers/api/v1alpha1/image.go index f80691124..d63ba89c8 100644 --- a/pkg/clients/listers/api/v1alpha1/image.go +++ b/pkg/clients/listers/api/v1alpha1/image.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/keypair.go b/pkg/clients/listers/api/v1alpha1/keypair.go index 6283d582e..268c14ac7 100644 --- a/pkg/clients/listers/api/v1alpha1/keypair.go +++ b/pkg/clients/listers/api/v1alpha1/keypair.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/network.go b/pkg/clients/listers/api/v1alpha1/network.go index 3039921cd..cdd26de8a 100644 --- a/pkg/clients/listers/api/v1alpha1/network.go +++ b/pkg/clients/listers/api/v1alpha1/network.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/port.go b/pkg/clients/listers/api/v1alpha1/port.go index 4a1748403..a61984e89 100644 --- a/pkg/clients/listers/api/v1alpha1/port.go +++ b/pkg/clients/listers/api/v1alpha1/port.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/project.go b/pkg/clients/listers/api/v1alpha1/project.go index 56fd8b6a3..c2dd486be 100644 --- a/pkg/clients/listers/api/v1alpha1/project.go +++ b/pkg/clients/listers/api/v1alpha1/project.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/role.go b/pkg/clients/listers/api/v1alpha1/role.go index 2068923b2..c2d62f895 100644 --- a/pkg/clients/listers/api/v1alpha1/role.go +++ b/pkg/clients/listers/api/v1alpha1/role.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/router.go b/pkg/clients/listers/api/v1alpha1/router.go index 71c3ce837..5adceeb9b 100644 --- a/pkg/clients/listers/api/v1alpha1/router.go +++ b/pkg/clients/listers/api/v1alpha1/router.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/routerinterface.go b/pkg/clients/listers/api/v1alpha1/routerinterface.go index 285c89483..0a3712f4c 100644 --- a/pkg/clients/listers/api/v1alpha1/routerinterface.go +++ b/pkg/clients/listers/api/v1alpha1/routerinterface.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/securitygroup.go b/pkg/clients/listers/api/v1alpha1/securitygroup.go index 7d0748924..5868504a9 100644 --- a/pkg/clients/listers/api/v1alpha1/securitygroup.go +++ b/pkg/clients/listers/api/v1alpha1/securitygroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/server.go b/pkg/clients/listers/api/v1alpha1/server.go index c79cc772a..66d769baa 100644 --- a/pkg/clients/listers/api/v1alpha1/server.go +++ b/pkg/clients/listers/api/v1alpha1/server.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/servergroup.go b/pkg/clients/listers/api/v1alpha1/servergroup.go index d376dc119..a677d2564 100644 --- a/pkg/clients/listers/api/v1alpha1/servergroup.go +++ b/pkg/clients/listers/api/v1alpha1/servergroup.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/service.go b/pkg/clients/listers/api/v1alpha1/service.go index fc5902d0b..46c571406 100644 --- a/pkg/clients/listers/api/v1alpha1/service.go +++ b/pkg/clients/listers/api/v1alpha1/service.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/subnet.go b/pkg/clients/listers/api/v1alpha1/subnet.go index 751437886..c1f916198 100644 --- a/pkg/clients/listers/api/v1alpha1/subnet.go +++ b/pkg/clients/listers/api/v1alpha1/subnet.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/volume.go b/pkg/clients/listers/api/v1alpha1/volume.go index fda954e65..70d54efc5 100644 --- a/pkg/clients/listers/api/v1alpha1/volume.go +++ b/pkg/clients/listers/api/v1alpha1/volume.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/volumetype.go b/pkg/clients/listers/api/v1alpha1/volumetype.go index b2bc83536..3dcb8d773 100644 --- a/pkg/clients/listers/api/v1alpha1/volumetype.go +++ b/pkg/clients/listers/api/v1alpha1/volumetype.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. From e3b4d2107cb5a09c51bc2800b4e0b600ceac6a16 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Tue, 30 Dec 2025 18:30:26 +0200 Subject: [PATCH 009/121] Add metadata to server controller Add metadata field to server controller allowing setting metadata on servers created by orc --- api/v1alpha1/server_types.go | 40 ++++++++ api/v1alpha1/zz_generated.deepcopy.go | 40 ++++++++ cmd/models-schema/zz_generated.openapi.go | 99 ++++++++++++++++++- .../bases/openstack.k-orc.cloud_servers.yaml | 43 ++++++++ config/samples/openstack_v1alpha1_server.yaml | 5 + internal/controllers/server/actuator.go | 40 ++++++++ internal/controllers/server/status.go | 9 ++ .../tests/server-create-full/00-assert.yaml | 5 + .../00-create-resource.yaml | 5 + .../server/tests/server-update/00-assert.yaml | 1 + .../server/tests/server-update/01-assert.yaml | 5 + .../server-update/01-updated-resource.yaml | 5 + .../server/tests/server-update/02-assert.yaml | 1 + internal/osclients/compute.go | 9 ++ internal/osclients/mock/compute.go | 15 +++ .../api/v1alpha1/servermetadata.go | 48 +++++++++ .../api/v1alpha1/servermetadatastatus.go | 48 +++++++++ .../api/v1alpha1/serverresourcespec.go | 14 +++ .../api/v1alpha1/serverresourcestatus.go | 14 +++ .../applyconfiguration/internal/internal.go | 30 ++++++ pkg/clients/applyconfiguration/utils.go | 4 + website/docs/crd-reference.md | 36 +++++++ 22 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.go diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go index 7fefa5f38..72471e9b0 100644 --- a/api/v1alpha1/server_types.go +++ b/api/v1alpha1/server_types.go @@ -181,6 +181,27 @@ type ServerResourceSpec struct { // +listType=set // +optional Tags []ServerTag `json:"tags,omitempty"` + + // metadata is a list of metadata key-value pairs which will be set on the server. + // +kubebuilder:validation:MaxItems:=128 + // +listType=atomic + // +optional + Metadata []ServerMetadata `json:"metadata,omitempty"` +} + +// ServerMetadata represents a key-value pair for server metadata. +type ServerMetadata struct { + // key is the metadata key. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +required + Key string `json:"key,omitempty"` + + // value is the metadata value. + // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:MinLength:=1 + // +required + Value string `json:"value,omitempty"` } // +kubebuilder:validation:MinProperties:=1 @@ -261,4 +282,23 @@ type ServerResourceStatus struct { // +listType=atomic // +optional Tags []string `json:"tags,omitempty"` + + // metadata is the list of metadata key-value pairs on the resource. + // +kubebuilder:validation:MaxItems:=128 + // +listType=atomic + // +optional + Metadata []ServerMetadataStatus `json:"metadata,omitempty"` +} + +// ServerMetadataStatus represents a key-value pair for server metadata in status. +type ServerMetadataStatus struct { + // key is the metadata key. + // +kubebuilder:validation:MaxLength:=255 + // +optional + Key string `json:"key,omitempty"` + + // value is the metadata value. + // +kubebuilder:validation:MaxLength:=255 + // +optional + Value string `json:"value,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1b90b21c1..3e5d13fe4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -4194,6 +4194,36 @@ func (in *ServerList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerMetadata) DeepCopyInto(out *ServerMetadata) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerMetadata. +func (in *ServerMetadata) DeepCopy() *ServerMetadata { + if in == nil { + return nil + } + out := new(ServerMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerMetadataStatus) DeepCopyInto(out *ServerMetadataStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerMetadataStatus. +func (in *ServerMetadataStatus) DeepCopy() *ServerMetadataStatus { + if in == nil { + return nil + } + out := new(ServerMetadataStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerPortSpec) DeepCopyInto(out *ServerPortSpec) { *out = *in @@ -4256,6 +4286,11 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) { *out = make([]ServerTag, len(*in)) copy(*out, *in) } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make([]ServerMetadata, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerResourceSpec. @@ -4293,6 +4328,11 @@ func (in *ServerResourceStatus) DeepCopyInto(out *ServerResourceStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make([]ServerMetadataStatus, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerResourceStatus. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 80dc9b6c6..22c4aab73 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -175,6 +175,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceFixedIP": schema_openstack_resource_controller_v2_api_v1alpha1_ServerInterfaceFixedIP(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerInterfaceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerList": schema_openstack_resource_controller_v2_api_v1alpha1_ServerList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadataStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerPortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerPortSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(ref), @@ -8093,6 +8095,61 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerList(ref common. } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServerMetadata represents a key-value pair for server metadata.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the metadata key.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the metadata value.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadataStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServerMetadataStatus represents a key-value pair for server metadata in status.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the metadata key.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the metadata value.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ServerPortSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -8225,12 +8282,31 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref }, }, }, + "metadata": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "metadata is a list of metadata key-value pairs which will be set on the server.", + 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.ServerMetadata"), + }, + }, + }, + }, + }, }, Required: []string{"imageRef", "flavorRef", "ports"}, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerPortSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadata", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerPortSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec"}, } } @@ -8354,11 +8430,30 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(r }, }, }, + "metadata": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "metadata is the list of metadata key-value pairs on the resource.", + 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.ServerMetadataStatus"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadataStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index c9feaab67..fd28e8b28 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -231,6 +231,30 @@ spec: x-kubernetes-validations: - message: keypairRef is immutable rule: self == oldSelf + metadata: + description: metadata is a list of metadata key-value pairs which + will be set on the server. + items: + description: ServerMetadata represents a key-value pair for + server metadata. + properties: + key: + description: key is the metadata key. + maxLength: 255 + minLength: 1 + type: string + value: + description: value is the metadata value. + maxLength: 255 + minLength: 1 + type: string + required: + - key + - value + type: object + maxItems: 128 + type: array + x-kubernetes-list-type: atomic name: description: |- name will be the name of the created resource. If not specified, the @@ -488,6 +512,25 @@ spec: maxItems: 64 type: array x-kubernetes-list-type: atomic + metadata: + description: metadata is the list of metadata key-value pairs + on the resource. + items: + description: ServerMetadataStatus represents a key-value pair + for server metadata in status. + properties: + key: + description: key is the metadata key. + maxLength: 255 + type: string + value: + description: value is the metadata value. + maxLength: 255 + type: string + type: object + maxItems: 128 + type: array + x-kubernetes-list-type: atomic name: description: name is the human-readable name of the resource. Might not be unique. diff --git a/config/samples/openstack_v1alpha1_server.yaml b/config/samples/openstack_v1alpha1_server.yaml index 29691f536..b1e737d43 100644 --- a/config/samples/openstack_v1alpha1_server.yaml +++ b/config/samples/openstack_v1alpha1_server.yaml @@ -20,3 +20,8 @@ spec: tags: - tag1 - tag2 + metadata: + - key: environment + value: development + - key: owner + value: sample diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index c0aefdb2d..2e14925d7 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -249,6 +249,11 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp // Sort tags before creation to simplify comparisons slices.Sort(tags) + metadata := make(map[string]string) + for _, m := range resource.Metadata { + metadata[m.Key] = m.Value + } + serverCreateOpts := servers.CreateOpts{ Name: getResourceName(obj), ImageRef: *image.Status.ID, @@ -256,6 +261,7 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp Networks: portList, UserData: userData, Tags: tags, + Metadata: metadata, AvailabilityZone: resource.AvailabilityZone, } @@ -307,6 +313,7 @@ func (actuator serverActuator) GetResourceReconcilers(ctx context.Context, orcOb actuator.checkStatus, actuator.updateResource, actuator.reconcileTags, + actuator.reconcileMetadata, actuator.reconcilePortAttachments, actuator.reconcileVolumeAttachments, }, nil @@ -393,6 +400,39 @@ func (actuator serverActuator) reconcileTags(ctx context.Context, obj orcObjectP return tags.ReconcileTags[orcObjectPT, osResourceT](obj.Spec.Resource.Tags, ptr.Deref(osResource.Tags, []string{}), tags.NewServerTagReplacer(actuator.osClient, osResource.ID))(ctx, obj, osResource) } +func (actuator serverActuator) reconcileMetadata(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) + } + + // Metadata cannot be set on a server that is still building + if osResource.Status == "" || osResource.Status == ServerStatusBuild { + return progress.NewReconcileStatus().WaitingOnOpenStack(progress.WaitingOnReady, serverActivePollingPeriod) + } + + // Build the desired metadata map from spec + desiredMetadata := make(map[string]string) + for _, m := range resource.Metadata { + desiredMetadata[m.Key] = m.Value + } + + // Compare with current metadata + if maps.Equal(desiredMetadata, osResource.Metadata) { + return nil + } + + log.V(logging.Verbose).Info("Updating server metadata") + _, err := actuator.osClient.ReplaceServerMetadata(ctx, osResource.ID, desiredMetadata) + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + func (actuator serverActuator) reconcilePortAttachments(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { log := ctrl.LoggerFrom(ctx) resource := obj.Spec.Resource diff --git a/internal/controllers/server/status.go b/internal/controllers/server/status.go index aa7c47ccf..b797e060c 100644 --- a/internal/controllers/server/status.go +++ b/internal/controllers/server/status.go @@ -18,6 +18,8 @@ package server import ( "fmt" + "maps" + "slices" "github.com/go-logr/logr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -97,5 +99,12 @@ func (serverStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes status.WithInterfaces(interfaceStatus) } + // Sort metadata keys for deterministic output + for _, k := range slices.Sorted(maps.Keys(osResource.Metadata)) { + status.WithMetadata(orcapplyconfigv1alpha1.ServerMetadataStatus(). + WithKey(k). + WithValue(osResource.Metadata[k])) + } + statusApply.WithResource(status) } diff --git a/internal/controllers/server/tests/server-create-full/00-assert.yaml b/internal/controllers/server/tests/server-create-full/00-assert.yaml index 5f351d6d1..751ea6c67 100644 --- a/internal/controllers/server/tests/server-create-full/00-assert.yaml +++ b/internal/controllers/server/tests/server-create-full/00-assert.yaml @@ -66,3 +66,8 @@ status: tags: - tag1 - tag2 + metadata: + - key: environment + value: test + - key: owner + value: kuttl diff --git a/internal/controllers/server/tests/server-create-full/00-create-resource.yaml b/internal/controllers/server/tests/server-create-full/00-create-resource.yaml index 006b18145..5b34af20b 100644 --- a/internal/controllers/server/tests/server-create-full/00-create-resource.yaml +++ b/internal/controllers/server/tests/server-create-full/00-create-resource.yaml @@ -48,3 +48,8 @@ spec: tags: - tag1 - tag2 + metadata: + - key: environment + value: test + - key: owner + value: kuttl diff --git a/internal/controllers/server/tests/server-update/00-assert.yaml b/internal/controllers/server/tests/server-update/00-assert.yaml index 551244650..6964361e5 100644 --- a/internal/controllers/server/tests/server-update/00-assert.yaml +++ b/internal/controllers/server/tests/server-update/00-assert.yaml @@ -24,6 +24,7 @@ assertAll: - celExpr: "server.status.resource.serverGroups[0] == sg.status.id" - celExpr: "!has(server.status.resource.tags)" - celExpr: "!has(server.status.resource.volumes)" + - celExpr: "!has(server.status.resource.metadata)" - celExpr: "size(server.status.resource.interfaces) == 1" - celExpr: "server.status.resource.interfaces[0].portID == port.status.id" --- diff --git a/internal/controllers/server/tests/server-update/01-assert.yaml b/internal/controllers/server/tests/server-update/01-assert.yaml index 473aecab0..db83497d8 100644 --- a/internal/controllers/server/tests/server-update/01-assert.yaml +++ b/internal/controllers/server/tests/server-update/01-assert.yaml @@ -54,6 +54,11 @@ status: tags: - tag1 - tag2 + metadata: + - key: environment + value: staging + - key: team + value: platform conditions: - type: Available status: "True" diff --git a/internal/controllers/server/tests/server-update/01-updated-resource.yaml b/internal/controllers/server/tests/server-update/01-updated-resource.yaml index 248b328a2..ae0cac6df 100644 --- a/internal/controllers/server/tests/server-update/01-updated-resource.yaml +++ b/internal/controllers/server/tests/server-update/01-updated-resource.yaml @@ -44,3 +44,8 @@ spec: tags: - tag1 - tag2 + metadata: + - key: environment + value: staging + - key: team + value: platform diff --git a/internal/controllers/server/tests/server-update/02-assert.yaml b/internal/controllers/server/tests/server-update/02-assert.yaml index 68beeb722..ec2db2777 100644 --- a/internal/controllers/server/tests/server-update/02-assert.yaml +++ b/internal/controllers/server/tests/server-update/02-assert.yaml @@ -32,6 +32,7 @@ assertAll: - celExpr: "server.status.resource.serverGroups[0] == sg.status.id" - celExpr: "!has(server.status.resource.tags)" - celExpr: "!has(server.status.resource.volumes)" + - celExpr: "!has(server.status.resource.metadata)" - celExpr: "!has(volume.status.resource.attachments)" - celExpr: "port1.status.resource.deviceID == server.status.id" - celExpr: "port1.status.resource.status == 'ACTIVE'" diff --git a/internal/osclients/compute.go b/internal/osclients/compute.go index e40154150..43f4396d5 100644 --- a/internal/osclients/compute.go +++ b/internal/osclients/compute.go @@ -72,6 +72,7 @@ type ComputeClient interface { DeleteAttachedInterface(ctx context.Context, serverID, portID string) error ReplaceAllServerAttributesTags(ctx context.Context, resourceID string, opts tags.ReplaceAllOptsBuilder) ([]string, error) + ReplaceServerMetadata(ctx context.Context, serverID string, opts servers.MetadataOpts) (map[string]string, error) } type computeClient struct{ client *gophercloud.ServiceClient } @@ -187,6 +188,10 @@ func (c computeClient) ReplaceAllServerAttributesTags(ctx context.Context, resou return tags.ReplaceAll(ctx, c.client, resourceID, opts).Extract() } +func (c computeClient) ReplaceServerMetadata(ctx context.Context, serverID string, opts servers.MetadataOpts) (map[string]string, error) { + return servers.ResetMetadata(ctx, c.client, serverID, opts).Extract() +} + type computeErrorClient struct{ error } // NewComputeErrorClient returns a ComputeClient in which every method returns the given error. @@ -275,3 +280,7 @@ func (e computeErrorClient) DeleteAttachedInterface(_ context.Context, _, _ stri func (e computeErrorClient) ReplaceAllServerAttributesTags(_ context.Context, _ string, _ tags.ReplaceAllOptsBuilder) ([]string, error) { return nil, e.error } + +func (e computeErrorClient) ReplaceServerMetadata(_ context.Context, _ string, _ servers.MetadataOpts) (map[string]string, error) { + return nil, e.error +} diff --git a/internal/osclients/mock/compute.go b/internal/osclients/mock/compute.go index bb85f073c..2fdea0de4 100644 --- a/internal/osclients/mock/compute.go +++ b/internal/osclients/mock/compute.go @@ -324,6 +324,21 @@ func (mr *MockComputeClientMockRecorder) ReplaceAllServerAttributesTags(ctx, res return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceAllServerAttributesTags", reflect.TypeOf((*MockComputeClient)(nil).ReplaceAllServerAttributesTags), ctx, resourceID, opts) } +// ReplaceServerMetadata mocks base method. +func (m *MockComputeClient) ReplaceServerMetadata(ctx context.Context, serverID string, opts servers.MetadataOpts) (map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReplaceServerMetadata", ctx, serverID, opts) + ret0, _ := ret[0].(map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReplaceServerMetadata indicates an expected call of ReplaceServerMetadata. +func (mr *MockComputeClientMockRecorder) ReplaceServerMetadata(ctx, serverID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceServerMetadata", reflect.TypeOf((*MockComputeClient)(nil).ReplaceServerMetadata), ctx, serverID, opts) +} + // UpdateServer mocks base method. func (m *MockComputeClient) UpdateServer(ctx context.Context, id string, opts servers.UpdateOptsBuilder) (*servers.Server, error) { m.ctrl.T.Helper() diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.go b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.go new file mode 100644 index 000000000..7d332a991 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.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 + +// ServerMetadataApplyConfiguration represents a declarative configuration of the ServerMetadata type for use +// with apply. +type ServerMetadataApplyConfiguration struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +} + +// ServerMetadataApplyConfiguration constructs a declarative configuration of the ServerMetadata type for use with +// apply. +func ServerMetadata() *ServerMetadataApplyConfiguration { + return &ServerMetadataApplyConfiguration{} +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *ServerMetadataApplyConfiguration) WithKey(value string) *ServerMetadataApplyConfiguration { + b.Key = &value + return b +} + +// WithValue sets the Value field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Value field is set to the value of the last call. +func (b *ServerMetadataApplyConfiguration) WithValue(value string) *ServerMetadataApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.go new file mode 100644 index 000000000..0f978fa89 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.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 + +// ServerMetadataStatusApplyConfiguration represents a declarative configuration of the ServerMetadataStatus type for use +// with apply. +type ServerMetadataStatusApplyConfiguration struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +} + +// ServerMetadataStatusApplyConfiguration constructs a declarative configuration of the ServerMetadataStatus type for use with +// apply. +func ServerMetadataStatus() *ServerMetadataStatusApplyConfiguration { + return &ServerMetadataStatusApplyConfiguration{} +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *ServerMetadataStatusApplyConfiguration) WithKey(value string) *ServerMetadataStatusApplyConfiguration { + b.Key = &value + return b +} + +// WithValue sets the Value field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Value field is set to the value of the last call. +func (b *ServerMetadataStatusApplyConfiguration) WithValue(value string) *ServerMetadataStatusApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go index bc0e6e435..99103626a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go @@ -35,6 +35,7 @@ type ServerResourceSpecApplyConfiguration struct { AvailabilityZone *string `json:"availabilityZone,omitempty"` KeypairRef *apiv1alpha1.KubernetesNameRef `json:"keypairRef,omitempty"` Tags []apiv1alpha1.ServerTag `json:"tags,omitempty"` + Metadata []ServerMetadataApplyConfiguration `json:"metadata,omitempty"` } // ServerResourceSpecApplyConfiguration constructs a declarative configuration of the ServerResourceSpec type for use with @@ -134,3 +135,16 @@ func (b *ServerResourceSpecApplyConfiguration) WithTags(values ...apiv1alpha1.Se } return b } + +// WithMetadata adds the given value to the Metadata 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 Metadata field. +func (b *ServerResourceSpecApplyConfiguration) WithMetadata(values ...*ServerMetadataApplyConfiguration) *ServerResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMetadata") + } + b.Metadata = append(b.Metadata, *values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go index 7d2c173cc..17c71f28d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go @@ -30,6 +30,7 @@ type ServerResourceStatusApplyConfiguration struct { Volumes []ServerVolumeStatusApplyConfiguration `json:"volumes,omitempty"` Interfaces []ServerInterfaceStatusApplyConfiguration `json:"interfaces,omitempty"` Tags []string `json:"tags,omitempty"` + Metadata []ServerMetadataStatusApplyConfiguration `json:"metadata,omitempty"` } // ServerResourceStatusApplyConfiguration constructs a declarative configuration of the ServerResourceStatus type for use with @@ -123,3 +124,16 @@ func (b *ServerResourceStatusApplyConfiguration) WithTags(values ...string) *Ser } return b } + +// WithMetadata adds the given value to the Metadata 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 Metadata field. +func (b *ServerResourceStatusApplyConfiguration) WithMetadata(values ...*ServerMetadataStatusApplyConfiguration) *ServerResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMetadata") + } + b.Metadata = append(b.Metadata, *values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 6a989754e..a370d84c6 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2355,6 +2355,24 @@ var schemaYAML = typed.YAMLObject(`types: - name: portState type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadata + map: + fields: + - name: key + type: + scalar: string + - name: value + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadataStatus + map: + fields: + - name: key + type: + scalar: string + - name: value + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerPortSpec map: fields: @@ -2376,6 +2394,12 @@ var schemaYAML = typed.YAMLObject(`types: - name: keypairRef type: scalar: string + - name: metadata + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadata + elementRelationship: atomic - name: name type: scalar: string @@ -2421,6 +2445,12 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerInterfaceStatus elementRelationship: atomic + - name: metadata + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadataStatus + elementRelationship: atomic - name: name type: scalar: string diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 478b73a00..5a3990951 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -292,6 +292,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ServerInterfaceFixedIPApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerInterfaceStatus"): return &apiv1alpha1.ServerInterfaceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ServerMetadata"): + return &apiv1alpha1.ServerMetadataApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ServerMetadataStatus"): + return &apiv1alpha1.ServerMetadataStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerPortSpec"): return &apiv1alpha1.ServerPortSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerResourceSpec"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index da7cd698d..56ff2422a 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -3293,6 +3293,40 @@ _Appears in:_ | `fixedIPs` _[ServerInterfaceFixedIP](#serverinterfacefixedip) array_ | fixedIPs is the list of fixed IP addresses assigned to the interface. | | MaxItems: 32
| +#### ServerMetadata + + + +ServerMetadata represents a key-value pair for server metadata. + + + +_Appears in:_ +- [ServerResourceSpec](#serverresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `key` _string_ | key is the metadata key. | | MaxLength: 255
MinLength: 1
| +| `value` _string_ | value is the metadata value. | | MaxLength: 255
MinLength: 1
| + + +#### ServerMetadataStatus + + + +ServerMetadataStatus represents a key-value pair for server metadata in status. + + + +_Appears in:_ +- [ServerResourceStatus](#serverresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `key` _string_ | key is the metadata key. | | MaxLength: 255
| +| `value` _string_ | value is the metadata value. | | MaxLength: 255
| + + #### ServerPortSpec @@ -3334,6 +3368,7 @@ _Appears in:_ | `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the server. | | MaxLength: 255
| | `keypairRef` _[KubernetesNameRef](#kubernetesnameref)_ | keypairRef is a reference to a KeyPair object. The server will be
created with this keypair for SSH access. | | MaxLength: 253
MinLength: 1
| | `tags` _[ServerTag](#servertag) array_ | tags is a list of tags which will be applied to the server. | | MaxItems: 50
MaxLength: 80
MinLength: 1
| +| `metadata` _[ServerMetadata](#servermetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
| #### ServerResourceStatus @@ -3358,6 +3393,7 @@ _Appears in:_ | `volumes` _[ServerVolumeStatus](#servervolumestatus) array_ | volumes contains the volumes attached to the server. | | MaxItems: 64
| | `interfaces` _[ServerInterfaceStatus](#serverinterfacestatus) array_ | interfaces contains the list of interfaces attached to the server. | | MaxItems: 64
| | `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 50
items:MaxLength: 1024
| +| `metadata` _[ServerMetadataStatus](#servermetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
| #### ServerSpec From 464aaf58d7a816304da6698a679960d6b828e6df Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 31 Dec 2025 09:00:23 +0200 Subject: [PATCH 010/121] Add config drive field to server controller --- api/v1alpha1/server_types.go | 11 +++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ cmd/models-schema/zz_generated.openapi.go | 14 ++++++++++++++ .../crd/bases/openstack.k-orc.cloud_servers.yaml | 13 +++++++++++++ config/samples/openstack_v1alpha1_server.yaml | 1 + internal/controllers/server/actuator.go | 1 + internal/controllers/server/status.go | 3 ++- .../server/tests/server-create-full/00-assert.yaml | 1 + .../server-create-full/00-create-resource.yaml | 1 + .../api/v1alpha1/serverresourcespec.go | 9 +++++++++ .../api/v1alpha1/serverresourcestatus.go | 9 +++++++++ .../applyconfiguration/internal/internal.go | 6 ++++++ website/docs/crd-reference.md | 2 ++ 13 files changed, 75 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go index 72471e9b0..f4f40b279 100644 --- a/api/v1alpha1/server_types.go +++ b/api/v1alpha1/server_types.go @@ -187,6 +187,13 @@ type ServerResourceSpec struct { // +listType=atomic // +optional Metadata []ServerMetadata `json:"metadata,omitempty"` + + // configDrive specifies whether to attach a config drive to the server. + // When true, configuration data will be available via a special drive + // instead of the metadata service. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="configDrive is immutable" + ConfigDrive *bool `json:"configDrive,omitempty"` } // ServerMetadata represents a key-value pair for server metadata. @@ -288,6 +295,10 @@ type ServerResourceStatus struct { // +listType=atomic // +optional Metadata []ServerMetadataStatus `json:"metadata,omitempty"` + + // configDrive indicates whether the server was booted with a config drive. + // +optional + ConfigDrive bool `json:"configDrive,omitempty"` } // ServerMetadataStatus represents a key-value pair for server metadata in status. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3e5d13fe4..74fd9dcdf 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -4291,6 +4291,11 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) { *out = make([]ServerMetadata, len(*in)) copy(*out, *in) } + if in.ConfigDrive != nil { + in, out := &in.ConfigDrive, &out.ConfigDrive + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerResourceSpec. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index adfa1133a..3b90d275e 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -8315,6 +8315,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref }, }, }, + "configDrive": { + SchemaProps: spec.SchemaProps{ + Description: "configDrive specifies whether to attach a config drive to the server. When true, configuration data will be available via a special drive instead of the metadata service.", + Type: []string{"boolean"}, + Format: "", + }, + }, }, Required: []string{"imageRef", "flavorRef", "ports"}, }, @@ -8463,6 +8470,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(r }, }, }, + "configDrive": { + SchemaProps: spec.SchemaProps{ + Description: "configDrive indicates whether the server was booted with a config drive.", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index fd28e8b28..2a882bad3 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -202,6 +202,15 @@ spec: x-kubernetes-validations: - message: availabilityZone is immutable rule: self == oldSelf + configDrive: + description: |- + configDrive specifies whether to attach a config drive to the server. + When true, configuration data will be available via a special drive + instead of the metadata service. + type: boolean + x-kubernetes-validations: + - message: configDrive is immutable + rule: self == oldSelf flavorRef: description: flavorRef references the flavor to use for the server instance. @@ -455,6 +464,10 @@ spec: server is located. maxLength: 1024 type: string + configDrive: + description: configDrive indicates whether the server was booted + with a config drive. + type: boolean hostID: description: hostID is the host where the server is located in the cloud. diff --git a/config/samples/openstack_v1alpha1_server.yaml b/config/samples/openstack_v1alpha1_server.yaml index b1e737d43..382d6f9b4 100644 --- a/config/samples/openstack_v1alpha1_server.yaml +++ b/config/samples/openstack_v1alpha1_server.yaml @@ -25,3 +25,4 @@ spec: value: development - key: owner value: sample + configDrive: true diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index 2e14925d7..acadd0be1 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -263,6 +263,7 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp Tags: tags, Metadata: metadata, AvailabilityZone: resource.AvailabilityZone, + ConfigDrive: resource.ConfigDrive, } /* keypairs.CreateOptsExt was merged into servers.CreateOpts in gopher cloud V3 diff --git a/internal/controllers/server/status.go b/internal/controllers/server/status.go index b797e060c..39956c531 100644 --- a/internal/controllers/server/status.go +++ b/internal/controllers/server/status.go @@ -71,7 +71,8 @@ func (serverStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes WithHostID(osResource.HostID). WithAvailabilityZone(osResource.AvailabilityZone). WithServerGroups(ptr.Deref(osResource.ServerGroups, []string{})...). - WithTags(ptr.Deref(osResource.Tags, []string{})...) + WithTags(ptr.Deref(osResource.Tags, []string{})...). + WithConfigDrive(osResource.ConfigDrive) if imageID, ok := osResource.Image["id"]; ok { status.WithImageID(fmt.Sprintf("%s", imageID)) diff --git a/internal/controllers/server/tests/server-create-full/00-assert.yaml b/internal/controllers/server/tests/server-create-full/00-assert.yaml index 751ea6c67..68c65c73b 100644 --- a/internal/controllers/server/tests/server-create-full/00-assert.yaml +++ b/internal/controllers/server/tests/server-create-full/00-assert.yaml @@ -71,3 +71,4 @@ status: value: test - key: owner value: kuttl + configDrive: true diff --git a/internal/controllers/server/tests/server-create-full/00-create-resource.yaml b/internal/controllers/server/tests/server-create-full/00-create-resource.yaml index 5b34af20b..6f82c53f2 100644 --- a/internal/controllers/server/tests/server-create-full/00-create-resource.yaml +++ b/internal/controllers/server/tests/server-create-full/00-create-resource.yaml @@ -53,3 +53,4 @@ spec: value: test - key: owner value: kuttl + configDrive: true diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go index 99103626a..c3308477a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go @@ -36,6 +36,7 @@ type ServerResourceSpecApplyConfiguration struct { KeypairRef *apiv1alpha1.KubernetesNameRef `json:"keypairRef,omitempty"` Tags []apiv1alpha1.ServerTag `json:"tags,omitempty"` Metadata []ServerMetadataApplyConfiguration `json:"metadata,omitempty"` + ConfigDrive *bool `json:"configDrive,omitempty"` } // ServerResourceSpecApplyConfiguration constructs a declarative configuration of the ServerResourceSpec type for use with @@ -148,3 +149,11 @@ func (b *ServerResourceSpecApplyConfiguration) WithMetadata(values ...*ServerMet } return b } + +// WithConfigDrive sets the ConfigDrive field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConfigDrive field is set to the value of the last call. +func (b *ServerResourceSpecApplyConfiguration) WithConfigDrive(value bool) *ServerResourceSpecApplyConfiguration { + b.ConfigDrive = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go index 17c71f28d..12f1a1032 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go @@ -31,6 +31,7 @@ type ServerResourceStatusApplyConfiguration struct { Interfaces []ServerInterfaceStatusApplyConfiguration `json:"interfaces,omitempty"` Tags []string `json:"tags,omitempty"` Metadata []ServerMetadataStatusApplyConfiguration `json:"metadata,omitempty"` + ConfigDrive *bool `json:"configDrive,omitempty"` } // ServerResourceStatusApplyConfiguration constructs a declarative configuration of the ServerResourceStatus type for use with @@ -137,3 +138,11 @@ func (b *ServerResourceStatusApplyConfiguration) WithMetadata(values ...*ServerM } return b } + +// WithConfigDrive sets the ConfigDrive field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConfigDrive field is set to the value of the last call. +func (b *ServerResourceStatusApplyConfiguration) WithConfigDrive(value bool) *ServerResourceStatusApplyConfiguration { + b.ConfigDrive = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 9509d7db0..abaeca27f 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2391,6 +2391,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: availabilityZone type: scalar: string + - name: configDrive + type: + scalar: boolean - name: flavorRef type: scalar: string @@ -2439,6 +2442,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: availabilityZone type: scalar: string + - name: configDrive + type: + scalar: boolean - name: hostID type: scalar: string diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 9330a0bed..32018f962 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -3371,6 +3371,7 @@ _Appears in:_ | `keypairRef` _[KubernetesNameRef](#kubernetesnameref)_ | keypairRef is a reference to a KeyPair object. The server will be
created with this keypair for SSH access. | | MaxLength: 253
MinLength: 1
| | `tags` _[ServerTag](#servertag) array_ | tags is a list of tags which will be applied to the server. | | MaxItems: 50
MaxLength: 80
MinLength: 1
| | `metadata` _[ServerMetadata](#servermetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
| +| `configDrive` _boolean_ | configDrive specifies whether to attach a config drive to the server.
When true, configuration data will be available via a special drive
instead of the metadata service. | | | #### ServerResourceStatus @@ -3396,6 +3397,7 @@ _Appears in:_ | `interfaces` _[ServerInterfaceStatus](#serverinterfacestatus) array_ | interfaces contains the list of interfaces attached to the server. | | MaxItems: 64
| | `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 50
items:MaxLength: 1024
| | `metadata` _[ServerMetadataStatus](#servermetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
| +| `configDrive` _boolean_ | configDrive indicates whether the server was booted with a config drive. | | | #### ServerSpec From 7127118abe23a8bd9abe6eb3bb57b90f79d399de Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Fri, 9 Jan 2026 19:13:04 +0000 Subject: [PATCH 011/121] Scaffolder Minor Bug: Whitespace appearing at the top of line in dependency/00-create-resources-missing-deps.yaml when using scaffolding tool to create a controller with -optional-create-dependency, but without -required-create-dependency. Why Fix it: Should want to fix the template because this will generate an obscure warning when running the kuttl tests. What Fixed it: Replaced left trim with a right trim at the end of requiredCreateDependencies loop and the beginning of the next loop OptionalCreateDependencies. Signed-off-by: Daniel Lawton --- .../00-create-resources-missing-deps.yaml.template | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/scaffold-controller/data/tests/dependency/00-create-resources-missing-deps.yaml.template b/cmd/scaffold-controller/data/tests/dependency/00-create-resources-missing-deps.yaml.template index b07a4666c..d58086401 100644 --- a/cmd/scaffold-controller/data/tests/dependency/00-create-resources-missing-deps.yaml.template +++ b/cmd/scaffold-controller/data/tests/dependency/00-create-resources-missing-deps.yaml.template @@ -9,7 +9,7 @@ metadata: name: {{ $packageName }}-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -25,7 +25,7 @@ metadata: name: {{ $packageName }}-dependency-no-{{ . | lower }} spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -37,9 +37,9 @@ spec: {{ . | camelCase }}Ref: {{ $packageName }}-dependency {{- end }} # TODO(scaffolding): Add the necessary fields to create the resource -{{- end }} -{{- end }} -{{- range .OptionalCreateDependencies }} +{{ end -}} +{{ end -}} +{{ range .OptionalCreateDependencies -}} --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: {{ $kind }} @@ -47,7 +47,7 @@ metadata: name: {{ $packageName }}-dependency-no-{{ . | lower }} spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -65,7 +65,7 @@ metadata: name: {{ .PackageName }}-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: {{ .PackageName }}-dependency managementPolicy: managed From 7079db34c56b8f6ca93970a8bd367abd7dea222b Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Fri, 9 Jan 2026 20:42:14 +0000 Subject: [PATCH 012/121] Bug Fix: changed scaffold-controller to generate dependency E2E test for both optional-create-dependency field and required-create-dependency field. Signed-off-by: Daniel Lawton --- cmd/scaffold-controller/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/scaffold-controller/main.go b/cmd/scaffold-controller/main.go index 03de800d8..854c2bf21 100644 --- a/cmd/scaffold-controller/main.go +++ b/cmd/scaffold-controller/main.go @@ -202,7 +202,7 @@ func render(srcDir, distDir string, resource *templateFields) { for _, file := range files { if file.IsDir() { - if file.Name() == "dependency" && len(resource.OptionalCreateDependencies) == 0 { + if file.Name() == "dependency" && len(resource.AllCreateDependencies) == 0 { continue } if file.Name() == "import-dependency" && len(resource.ImportDependencies) == 0 { From 3c05d5352f9c004ce5b864fc07be228491e9be8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 18:03:48 +0000 Subject: [PATCH 013/121] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 3 updates Bumps the all-go-mod-patch-and-minor group with 3 updates in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo), [github.com/onsi/gomega](https://github.com/onsi/gomega) and [golang.org/x/text](https://github.com/golang/text). Updates `github.com/onsi/ginkgo/v2` from 2.27.3 to 2.27.4 - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.27.3...v2.27.4) Updates `github.com/onsi/gomega` from 1.38.3 to 1.39.0 - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.38.3...v1.39.0) Updates `golang.org/x/text` from 0.32.0 to 0.33.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.27.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: github.com/onsi/gomega dependency-version: 1.39.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor - dependency-name: golang.org/x/text dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 419ce1cbe..2eb8ea292 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,11 @@ require ( github.com/go-logr/logr v1.4.3 github.com/gophercloud/gophercloud/v2 v2.10.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.27.3 - github.com/onsi/gomega v1.38.3 + github.com/onsi/ginkgo/v2 v2.27.4 + github.com/onsi/gomega v1.39.0 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 - golang.org/x/text v0.32.0 + golang.org/x/text v0.33.0 k8s.io/api v0.34.3 k8s.io/apimachinery v0.34.3 k8s.io/client-go v0.34.3 @@ -84,14 +84,14 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index 985aa862c..d5c4beaa2 100644 --- a/go.sum +++ b/go.sum @@ -117,10 +117,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8= -github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= -github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= +github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -207,14 +207,14 @@ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -225,22 +225,22 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= From df9c8a16fbf21c55fb15c566800e1f6888422b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 13 Jan 2026 15:00:39 +0100 Subject: [PATCH 014/121] scaffolding: fix resource name in template --- .../data/tests/create-minimal/00-create-resource.yaml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template index 505a48883..281c9e463 100644 --- a/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template @@ -30,7 +30,7 @@ spec: {{- if len .RequiredCreateDependencies }} resource: {{- range .RequiredCreateDependencies }} - {{ . | camelCase }}Ref: {{ $packageName }}-create-full + {{ . | camelCase }}Ref: {{ $packageName }}-create-minimal {{- end }} {{- else }} resource: {} From 8c8422dd27d3e932c4a2c64f585eec6562a5c74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 13 Jan 2026 15:01:27 +0100 Subject: [PATCH 015/121] scaffolding: rename file for consistency --- .../{00-prerequisites.yaml.template => 00-secret.yaml.template} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/scaffold-controller/data/tests/update/{00-prerequisites.yaml.template => 00-secret.yaml.template} (100%) diff --git a/cmd/scaffold-controller/data/tests/update/00-prerequisites.yaml.template b/cmd/scaffold-controller/data/tests/update/00-secret.yaml.template similarity index 100% rename from cmd/scaffold-controller/data/tests/update/00-prerequisites.yaml.template rename to cmd/scaffold-controller/data/tests/update/00-secret.yaml.template From 6b101ec11b9bbb8dcd3d29055e52ecee3aa0555e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 14 Jan 2026 10:03:07 +0100 Subject: [PATCH 016/121] scaffolding: do not add required deps _and_ import deps Required deps is usually a subset of import deps. Generating resources for both caused duplicates whenever you use the same kind in both required dep and import dep. We should ideally have a union of both sets but that complicates the template without much benefit: the user will quickly notice if a required dep is missing and can add it easily. --- .../01-create-trap-resource.yaml.template | 18 ------------------ .../02-create-resource.yaml.template | 18 ------------------ 2 files changed, 36 deletions(-) diff --git a/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template index 4c6662c37..aa78dfb31 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template @@ -14,21 +14,6 @@ spec: # TODO(scaffolding): Add the necessary fields to create the resource resource: {} {{ end -}} -{{ range .RequiredCreateDependencies -}} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: {{ . }} -metadata: - name: {{ $packageName }}-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} -{{ end -}} --- # This `{{ $packageName }}-import-dependency-not-this-one` should not be picked by the import filter apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -42,9 +27,6 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: -{{- range .RequiredCreateDependencies }} - {{ . | camelCase }}Ref: {{ $packageName }}-import-dependency-not-this-one -{{- end }} {{- range .ImportDependencies }} {{ . | camelCase }}Ref: {{ $packageName }}-import-dependency-not-this-one {{- end }} diff --git a/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template index 206089c0b..cf4a7b3ad 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template @@ -1,19 +1,4 @@ {{ $packageName := .PackageName -}} -{{ range .RequiredCreateDependencies -}} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: {{ . }} -metadata: - name: {{ $packageName }}-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} -{{ end -}} {{ range .ImportDependencies -}} --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -41,9 +26,6 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: -{{- range .RequiredCreateDependencies }} - {{ . | camelCase }}Ref: {{ $packageName }}-import-dependency-external -{{- end }} {{- range .ImportDependencies }} {{ . | camelCase }}Ref: {{ $packageName }}-import-dependency-external {{- end }} From 457a0981fff79708ed9093a3fa57d262d60febb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 14 Jan 2026 10:14:30 +0100 Subject: [PATCH 017/121] scaffolding: fix typo --- .../data/tests/create-full/00-create-resource.yaml.template | 4 ++-- .../tests/create-minimal/00-create-resource.yaml.template | 4 ++-- .../tests/dependency/01-create-dependencies.yaml.template | 4 ++-- .../import-dependency/01-create-trap-resource.yaml.template | 4 ++-- .../import-dependency/02-create-resource.yaml.template | 4 ++-- .../tests/import-error/00-create-resources.yaml.template | 6 +++--- .../data/tests/import/01-create-trap-resource.yaml.template | 4 ++-- .../data/tests/import/02-create-resource.yaml.template | 4 ++-- .../data/tests/update/00-minimal-resource.yaml.template | 4 ++-- internal/controllers/port/actuator.go | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/scaffold-controller/data/tests/create-full/00-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/create-full/00-create-resource.yaml.template index 3598d91c5..06124a45b 100644 --- a/cmd/scaffold-controller/data/tests/create-full/00-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/create-full/00-create-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -21,7 +21,7 @@ metadata: name: {{ .PackageName }}-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template index 281c9e463..abb027cc0 100644 --- a/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/create-minimal/00-create-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -21,7 +21,7 @@ metadata: name: {{ .PackageName }}-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/dependency/01-create-dependencies.yaml.template b/cmd/scaffold-controller/data/tests/dependency/01-create-dependencies.yaml.template index abf7f84a6..aa706cf07 100644 --- a/cmd/scaffold-controller/data/tests/dependency/01-create-dependencies.yaml.template +++ b/cmd/scaffold-controller/data/tests/dependency/01-create-dependencies.yaml.template @@ -14,7 +14,7 @@ metadata: name: {{ $packageName }}-dependency-pending spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -29,7 +29,7 @@ metadata: name: {{ $packageName }}-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template index aa78dfb31..6414ec52f 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/01-create-trap-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -22,7 +22,7 @@ metadata: name: {{ $packageName }}-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template index cf4a7b3ad..3f7c4adb2 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -21,7 +21,7 @@ metadata: name: {{ $packageName }}-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/import-error/00-create-resources.yaml.template b/cmd/scaffold-controller/data/tests/import-error/00-create-resources.yaml.template index 8ff66bd03..79270d6b8 100644 --- a/cmd/scaffold-controller/data/tests/import-error/00-create-resources.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-error/00-create-resources.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-import-error spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -21,7 +21,7 @@ metadata: name: {{ .PackageName }}-import-error-external-1 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -38,7 +38,7 @@ metadata: name: {{ .PackageName }}-import-error-external-2 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/import/01-create-trap-resource.yaml.template b/cmd/scaffold-controller/data/tests/import/01-create-trap-resource.yaml.template index 4ab6adb51..86cfc4319 100644 --- a/cmd/scaffold-controller/data/tests/import/01-create-trap-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import/01-create-trap-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -24,7 +24,7 @@ metadata: name: {{ .PackageName }}-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/import/02-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/import/02-create-resource.yaml.template index 1b21ced42..7f6d07bc1 100644 --- a/cmd/scaffold-controller/data/tests/import/02-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import/02-create-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-import spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -21,7 +21,7 @@ metadata: name: {{ .PackageName }}-import-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/cmd/scaffold-controller/data/tests/update/00-minimal-resource.yaml.template b/cmd/scaffold-controller/data/tests/update/00-minimal-resource.yaml.template index d1c77fd50..28468b95d 100644 --- a/cmd/scaffold-controller/data/tests/update/00-minimal-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/update/00-minimal-resource.yaml.template @@ -7,7 +7,7 @@ metadata: name: {{ $packageName }}-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed @@ -21,7 +21,7 @@ metadata: name: {{ .PackageName }}-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated cloudName: openstack secretName: openstack-clouds managementPolicy: managed diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 822f6a5be..f4890c03a 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -521,10 +521,10 @@ func handlePortSecurityUpdate(updateOpts ports.UpdateOptsBuilder, resource *reso return updateOpts } -func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSpecT, osResouce *osResourceT) { +func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { adminStateUp := resource.AdminStateUp if adminStateUp != nil { - if *adminStateUp != osResouce.AdminStateUp { + if *adminStateUp != osResource.AdminStateUp { updateOpts.AdminStateUp = adminStateUp } } From c375dd2cab68fe094f95663a786791941574131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 14 Jan 2026 10:14:48 +0100 Subject: [PATCH 018/121] Fix sample domain config --- config/samples/openstack_v1alpha1_domain.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/samples/openstack_v1alpha1_domain.yaml b/config/samples/openstack_v1alpha1_domain.yaml index 7903d0fb8..0ea0bdbc3 100644 --- a/config/samples/openstack_v1alpha1_domain.yaml +++ b/config/samples/openstack_v1alpha1_domain.yaml @@ -5,10 +5,9 @@ metadata: name: domain-sample spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: description: Sample Domain - # TODO(scaffolding): Add all fields the resource supports + enabled: true From 997fbf2aa02e8bd2a7fd5883fe92267598290179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 16 Jan 2026 07:44:05 +0100 Subject: [PATCH 019/121] Bump kuttl This should get rid of the warnings because of README.md files in the test directories. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 37b4913a1..57bcd42fb 100644 --- a/Makefile +++ b/Makefile @@ -317,7 +317,7 @@ ENVTEST_VERSION ?= release-0.22 GOLANGCI_LINT_VERSION ?= v2.7.2 KAL_VERSION ?= v0.0.0-20250924094418-502783c08f9d MOCKGEN_VERSION ?= v0.5.0 -KUTTL_VERSION ?= v0.23.0 +KUTTL_VERSION ?= v0.24.0 GOVULNCHECK_VERSION ?= v1.1.4 OPERATOR_SDK_VERSION ?= v1.41.1 From 47d45c46a3d7f38c70e44e2a7781969bc02ac611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 16 Jan 2026 15:12:08 +0100 Subject: [PATCH 020/121] Port: change hostID to struct with id and serverRef fields This changes the Port API to use a structured hostID field instead of a simple string, allowing users to specify the host ID either directly or by referencing a Server resource. --- api/v1alpha1/port_types.go | 25 ++++++- api/v1alpha1/zz_generated.deepcopy.go | 20 ++++++ cmd/models-schema/zz_generated.openapi.go | 35 ++++++++-- .../bases/openstack.k-orc.cloud_ports.yaml | 28 +++++++- internal/controllers/port/actuator.go | 67 +++++++++++++++++-- internal/controllers/port/actuator_test.go | 2 +- internal/controllers/port/controller.go | 20 ++++++ .../port-create-full/00-create-resource.yaml | 3 +- .../port-update/01-updated-resource.yaml | 3 +- .../applyconfiguration/api/v1alpha1/hostid.go | 52 ++++++++++++++ .../api/v1alpha1/portresourcespec.go | 6 +- .../applyconfiguration/internal/internal.go | 11 ++- pkg/clients/applyconfiguration/utils.go | 2 + website/docs/crd-reference.md | 23 ++++++- 14 files changed, 273 insertions(+), 24 deletions(-) create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/hostid.go diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 9e51d0153..d54f3cf95 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -49,6 +49,26 @@ type PortFilter struct { FilterByNeutronTags `json:",inline"` } +// HostID specifies how to determine the host ID for port binding. +// Exactly one of the fields must be set. +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +// +kubebuilder:validation:XValidation:rule="(has(self.id) && size(self.id) > 0) != (has(self.serverRef) && size(self.serverRef) > 0)",message="exactly one of id or serverRef must be set" +type HostID struct { + // id is the literal host ID string to use for binding:host_id. + // This is mutually exclusive with serverRef. + // +kubebuilder:validation:MaxLength=36 + // +optional + ID string `json:"id,omitempty"` + + // serverRef is a reference to an ORC Server resource from which to + // retrieve the hostID for port binding. The hostID will be read from + // the Server's status.resource.hostID field. + // This is mutually exclusive with id. + // +optional + ServerRef KubernetesNameRef `json:"serverRef,omitempty"` +} + type AllowedAddressPair struct { // ip contains an IP address which a server connected to the port can // send packets with. It can be an IP Address or a CIDR (if supported @@ -181,10 +201,9 @@ type PortResourceSpec struct { // +optional MACAddress string `json:"macAddress,omitempty"` - // hostID is the ID of host where the port resides. - // +kubebuilder:validation:MaxLength=36 + // hostID specifies the host where the port will be bound. // +optional - HostID string `json:"hostID,omitempty"` + HostID *HostID `json:"hostID,omitempty"` } type PortResourceStatus struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 74fd9dcdf..1965cca7c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1235,6 +1235,21 @@ func (in *GroupStatus) DeepCopy() *GroupStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostID) DeepCopyInto(out *HostID) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostID. +func (in *HostID) DeepCopy() *HostID { + if in == nil { + return nil + } + out := new(HostID) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostRoute) DeepCopyInto(out *HostRoute) { *out = *in @@ -2529,6 +2544,11 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.HostID != nil { + in, out := &in.HostID, &out.HostID + *out = new(HostID) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortResourceSpec. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3b90d275e..7f65e1b99 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -74,6 +74,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_GroupResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_GroupSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_GroupStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID": schema_openstack_resource_controller_v2_api_v1alpha1_HostID(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostRoute": schema_openstack_resource_controller_v2_api_v1alpha1_HostRoute(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostRouteStatus": schema_openstack_resource_controller_v2_api_v1alpha1_HostRouteStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.IPv6Options": schema_openstack_resource_controller_v2_api_v1alpha1_IPv6Options(ref), @@ -2637,6 +2638,33 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_GroupStatus(ref common } } +func schema_openstack_resource_controller_v2_api_v1alpha1_HostID(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostID specifies how to determine the host ID for port binding. Exactly one of the fields must be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the literal host ID string to use for binding:host_id. This is mutually exclusive with serverRef.", + Type: []string{"string"}, + Format: "", + }, + }, + "serverRef": { + SchemaProps: spec.SchemaProps{ + Description: "serverRef is a reference to an ORC Server resource from which to retrieve the hostID for port binding. The hostID will be read from the Server's status.resource.hostID field. This is mutually exclusive with id.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_HostRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4913,9 +4941,8 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, "hostID": { SchemaProps: spec.SchemaProps{ - Description: "hostID is the ID of host where the port resides.", - Type: []string{"string"}, - Format: "", + Description: "hostID specifies the host where the port will be bound.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID"), }, }, }, @@ -4923,7 +4950,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Address", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Address", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index c14173d71..a533fefb8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -299,9 +299,31 @@ spec: minLength: 1 type: string hostID: - description: hostID is the ID of host where the port resides. - maxLength: 36 - type: string + description: hostID specifies the host where the port will be + bound. + maxProperties: 1 + minProperties: 1 + properties: + id: + description: |- + id is the literal host ID string to use for binding:host_id. + This is mutually exclusive with serverRef. + maxLength: 36 + type: string + serverRef: + description: |- + serverRef is a reference to an ORC Server resource from which to + retrieve the hostID for port binding. The hostID will be read from + the Server's status.resource.hostID field. + This is mutually exclusive with id. + maxLength: 253 + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: exactly one of id or serverRef must be set + rule: (has(self.id) && size(self.id) > 0) != (has(self.serverRef) + && size(self.serverRef) > 0) macAddress: description: macAddress is the MAC address of the port. maxLength: 32 diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index f4890c03a..983cddb18 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -57,6 +57,45 @@ const ( serverBuildPollingPeriod = 15 * time.Second ) +// resolveHostID resolves the actual host ID string to use for port binding. +// It handles both direct ID specification and server reference. +// Returns the resolved host ID and a reconcile status (for waiting on dependencies). +func resolveHostID( + ctx context.Context, + k8sClient client.Client, + obj orcObjectPT, + hostIDSpec *orcv1alpha1.HostID, +) (string, progress.ReconcileStatus) { + if hostIDSpec == nil { + return "", nil + } + + // Direct ID specification + if hostIDSpec.ID != "" { + return hostIDSpec.ID, nil + } + + // Server reference - fetch the server and extract its hostID + if hostIDSpec.ServerRef != "" { + server, serverDepRS := dependency.FetchDependency( + ctx, k8sClient, obj.Namespace, &hostIDSpec.ServerRef, "Server", + func(dep *orcv1alpha1.Server) bool { + return orcv1alpha1.IsAvailable(dep) && + dep.Status.Resource != nil && + dep.Status.Resource.HostID != "" + }, + ) + if needsReschedule, _ := serverDepRS.NeedsReschedule(); needsReschedule { + return "", serverDepRS + } + if server != nil && server.Status.Resource != nil { + return server.Status.Resource.HostID, nil + } + } + + return "", nil +} + type portActuator struct { osClient osclients.NetworkClient k8sClient client.Client @@ -166,6 +205,14 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha } } + // Resolve hostID if specified + var resolvedHostID string + if resource.HostID != nil { + var hostIDReconcileStatus progress.ReconcileStatus + resolvedHostID, hostIDReconcileStatus = resolveHostID(ctx, actuator.k8sClient, obj, resource.HostID) + reconcileStatus = reconcileStatus.WithReconcileStatus(hostIDReconcileStatus) + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } @@ -232,7 +279,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha portsBindingOpts := portsbinding.CreateOptsExt{ CreateOptsBuilder: createOpts, VNICType: resource.VNICType, - HostID: resource.HostID, + HostID: resolvedHostID, } portSecurityOpts := portsecurity.PortCreateOptsExt{ @@ -330,6 +377,14 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT reconcileStatus := progress.NewReconcileStatus(). WithReconcileStatus(secGroupDepRS) + // Resolve hostID if specified + var resolvedHostID string + if resource.HostID != nil { + var hostIDReconcileStatus progress.ReconcileStatus + resolvedHostID, hostIDReconcileStatus = resolveHostID(ctx, actuator.k8sClient, obj, resource.HostID) + reconcileStatus = reconcileStatus.WithReconcileStatus(hostIDReconcileStatus) + } + needsReschedule, _ := reconcileStatus.NeedsReschedule() if needsReschedule { return reconcileStatus @@ -348,7 +403,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT updateOpts = baseUpdateOpts } - updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource) + updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource, resolvedHostID) updateOpts = handlePortSecurityUpdate(updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) @@ -474,7 +529,7 @@ func handleSecurityGroupRefsUpdate(updateOpts *ports.UpdateOpts, resource *resou } } -func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT) ports.UpdateOptsBuilder { +func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT, resolvedHostID string) ports.UpdateOptsBuilder { if resource.VNICType != "" { if resource.VNICType != osResource.VNICType { updateOpts = &portsbinding.UpdateOptsExt{ @@ -484,11 +539,11 @@ func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resou } } - if resource.HostID != "" { - if resource.HostID != osResource.HostID { + if resolvedHostID != "" { + if resolvedHostID != osResource.HostID { updateOpts = &portsbinding.UpdateOptsExt{ UpdateOptsBuilder: updateOpts, - HostID: &resource.HostID, + HostID: &resolvedHostID, } } } diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index 81a2a7cc6..d6f7186c0 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -359,7 +359,7 @@ func TestHandlePortBindingUpdate(t *testing.T) { }, } - updateOpts := handlePortBindingUpdate(&ports.UpdateOpts{}, resource, osResource) + updateOpts := handlePortBindingUpdate(&ports.UpdateOpts{}, resource, osResource, "") got, _ := needsUpdate(updateOpts) if got != tt.expectChange { diff --git a/internal/controllers/port/controller.go b/internal/controllers/port/controller.go index 6191d84b3..ae0d73b37 100644 --- a/internal/controllers/port/controller.go +++ b/internal/controllers/port/controller.go @@ -127,6 +127,17 @@ var ( return []string{string(*resource.Filter.ProjectRef)} }, ) + + serverDependency = dependency.NewDependency[*orcv1alpha1.PortList, *orcv1alpha1.Server]( + "spec.resource.hostID.serverRef", + func(port *orcv1alpha1.Port) []string { + resource := port.Spec.Resource + if resource == nil || resource.HostID == nil || resource.HostID.ServerRef == "" { + return nil + } + return []string{string(resource.HostID.ServerRef)} + }, + ) ) // serverToPortMapFunc creates a mapping function that reconciles ports when: @@ -291,6 +302,11 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr return err } + serverWatchEventHandler, err := serverDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). For(&orcv1alpha1.Port{}). @@ -314,6 +330,9 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), ). + Watches(&orcv1alpha1.Server{}, serverWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Server{})), + ). Watches(&orcv1alpha1.Server{}, handler.EnqueueRequestsFromMapFunc(serverToPortMapFunc(ctx, k8sClient)), builder.WithPredicates(predicates.NewServerInterfacesChanged(log)), ) @@ -325,6 +344,7 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr securityGroupDependency.AddToManager(ctx, mgr), projectDependency.AddToManager(ctx, mgr), projectImportDependency.AddToManager(ctx, mgr), + serverDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml index bbb52641d..26e1d9d27 100644 --- a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml +++ b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml @@ -85,4 +85,5 @@ spec: vnicType: macvtap projectRef: port-create-full macAddress: fa:16:3e:23:fd:d7 - hostID: devstack + hostID: + id: devstack diff --git a/internal/controllers/port/tests/port-update/01-updated-resource.yaml b/internal/controllers/port/tests/port-update/01-updated-resource.yaml index 107b4ab5d..2af467f7c 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -31,4 +31,5 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - hostID: devstack + hostID: + id: devstack diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostid.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostid.go new file mode 100644 index 000000000..3f571fe0b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostid.go @@ -0,0 +1,52 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// HostIDApplyConfiguration represents a declarative configuration of the HostID type for use +// with apply. +type HostIDApplyConfiguration struct { + ID *string `json:"id,omitempty"` + ServerRef *apiv1alpha1.KubernetesNameRef `json:"serverRef,omitempty"` +} + +// HostIDApplyConfiguration constructs a declarative configuration of the HostID type for use with +// apply. +func HostID() *HostIDApplyConfiguration { + return &HostIDApplyConfiguration{} +} + +// 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 *HostIDApplyConfiguration) WithID(value string) *HostIDApplyConfiguration { + b.ID = &value + return b +} + +// WithServerRef sets the ServerRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServerRef field is set to the value of the last call. +func (b *HostIDApplyConfiguration) WithServerRef(value apiv1alpha1.KubernetesNameRef) *HostIDApplyConfiguration { + b.ServerRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index aab07d8bc..491a5c818 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -37,7 +37,7 @@ type PortResourceSpecApplyConfiguration struct { PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` MACAddress *string `json:"macAddress,omitempty"` - HostID *string `json:"hostID,omitempty"` + HostID *HostIDApplyConfiguration `json:"hostID,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -159,7 +159,7 @@ func (b *PortResourceSpecApplyConfiguration) WithMACAddress(value string) *PortR // WithHostID sets the HostID field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the HostID field is set to the value of the last call. -func (b *PortResourceSpecApplyConfiguration) WithHostID(value string) *PortResourceSpecApplyConfiguration { - b.HostID = &value +func (b *PortResourceSpecApplyConfiguration) WithHostID(value *HostIDApplyConfiguration) *PortResourceSpecApplyConfiguration { + b.HostID = value return b } diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index abaeca27f..f545b52e7 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -634,6 +634,15 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.GroupResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostID + map: + fields: + - name: id + type: + scalar: string + - name: serverRef + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostRoute map: fields: @@ -1335,7 +1344,7 @@ var schemaYAML = typed.YAMLObject(`types: scalar: string - name: hostID type: - scalar: string + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostID - name: macAddress type: scalar: string diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 5a3990951..3df1b92e4 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -112,6 +112,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.GroupSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("GroupStatus"): return &apiv1alpha1.GroupStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostID"): + return &apiv1alpha1.HostIDApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HostRoute"): return &apiv1alpha1.HostRouteApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HostRouteStatus"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 32018f962..9709d49a9 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -892,6 +892,26 @@ _Appears in:_ | `resource` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +#### HostID + + + +HostID specifies how to determine the host ID for port binding. +Exactly one of the fields must be set. + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [PortResourceSpec](#portresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id is the literal host ID string to use for binding:host_id.
This is mutually exclusive with serverRef. | | MaxLength: 36
| +| `serverRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverRef is a reference to an ORC Server resource from which to
retrieve the hostID for port binding. The hostID will be read from
the Server's status.resource.hostID field.
This is mutually exclusive with id. | | MaxLength: 253
MinLength: 1
| + + #### HostRoute @@ -1616,6 +1636,7 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [HostID](#hostid) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) @@ -2171,7 +2192,7 @@ _Appears in:_ | `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| | `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| -| `hostID` _string_ | hostID is the ID of host where the port resides. | | MaxLength: 36
| +| `hostID` _[HostID](#hostid)_ | hostID specifies the host where the port will be bound. | | MaxProperties: 1
MinProperties: 1
| #### PortResourceStatus From f622da12a58e969be7111aea99d00176ca80c50d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:18:22 +0000 Subject: [PATCH 021/121] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 2 updates Bumps the all-go-mod-patch-and-minor group with 2 updates in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime). Updates `github.com/onsi/ginkgo/v2` from 2.27.4 to 2.27.5 - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.27.4...v2.27.5) Updates `sigs.k8s.io/controller-runtime` from 0.22.4 to 0.22.5 - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.4...v0.22.5) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.27.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.22.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 2eb8ea292..57b18fb16 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-logr/logr v1.4.3 github.com/gophercloud/gophercloud/v2 v2.10.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.27.4 + github.com/onsi/ginkgo/v2 v2.27.5 github.com/onsi/gomega v1.39.0 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 @@ -19,7 +19,7 @@ require ( k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.22.4 + sigs.k8s.io/controller-runtime v0.22.5 sigs.k8s.io/structured-merge-diff/v6 v6.3.1 sigs.k8s.io/yaml v1.6.0 ) @@ -101,9 +101,9 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.34.1 // indirect - k8s.io/apiserver v0.34.1 // indirect - k8s.io/component-base v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/apiserver v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/go.sum b/go.sum index d5c4beaa2..8695293a5 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= -github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE= +github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -271,18 +271,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/code-generator v0.34.3 h1:6ipJKsJZZ9q21BO8I2jEj4OLN3y8/1n4aihKN0xKmQk= k8s.io/code-generator v0.34.3/go.mod h1:oW73UPYpGLsbRN8Ozkhd6ZzkF8hzFCiYmvEuWZDroI4= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= @@ -293,8 +293,8 @@ k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= -sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.22.5 h1:v3nfSUMowX/2WMp27J9slwGFyAt7IV0YwBxAkrUr0GE= +sigs.k8s.io/controller-runtime v0.22.5/go.mod h1:pc5SoYWnWI6I+cBHYYdZ7B6YHZVY5xNfll88JB+vniI= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= From 32143caeae098746cc8f22bb077db6f26cd05c0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:18:38 +0000 Subject: [PATCH 022/121] :seedling:(deps): Bump the all-github-actions group with 2 updates Bumps the all-github-actions group with 2 updates: [actions/setup-go](https://github.com/actions/setup-go) and [actions/cache](https://github.com/actions/cache). Updates `actions/setup-go` from 6.1.0 to 6.2.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/4dc6199c7b1a012772edbd06daecab0f50c9053c...7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5) Updates `actions/cache` from 5.0.1 to 5.0.2 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/9255dc7a253b0ccc959486e2bca901246202afeb...8b402f58fbc84540c8b491a91e594a4576fec3d7) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions - dependency-name: actions/cache dependency-version: 5.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/go-lint.yaml | 2 +- .github/workflows/label-pr.yaml | 2 +- .github/workflows/pr-dependabot.yaml | 4 ++-- .github/workflows/unit.yml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index d8673921e..37236a59e 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -20,7 +20,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # tag=v6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 975d02ad2..1df1d4e5d 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -32,7 +32,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # tag=v6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index 35ca7c99c..a111be029 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -24,10 +24,10 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # tag=v6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 with: go-version: ${{ steps.vars.outputs.go_version }} - - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # tag=v5.0.1 + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # tag=v5.0.2 name: Restore go cache with: path: | diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 1b8bf4604..b74657d8e 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -24,7 +24,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # tag=v6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index ccca71f45..abf76392d 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -25,7 +25,7 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # tag=v6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 with: go-version: ${{ steps.vars.outputs.go_version }} - name: Run verify security target From 0801402deb09472b1c8192f03bf746b784ca24bf Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 31 Dec 2025 14:15:10 +0200 Subject: [PATCH 023/121] Add imageRef to volume controller Enables creating bootable volumes from images by adding an imageRef field to the Volume spec. When specified, the volume is created with the image baked in, making it suitable for boot-from-volume scenarios. Changes: - Add imageRef field to VolumeResourceSpec - Add bootable and imageID fields to VolumeResourceStatus - Add image dependency with deletion guard - Add kuttl tests for bootable volume creation assisted-by: claude --- api/v1alpha1/volume_types.go | 12 ++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ cmd/models-schema/zz_generated.openapi.go | 14 ++++++++++++ .../bases/openstack.k-orc.cloud_volumes.yaml | 16 ++++++++++++++ config/samples/openstack_v1alpha1_volume.yaml | 1 + internal/controllers/volume/actuator.go | 13 +++++++++++ internal/controllers/volume/controller.go | 22 +++++++++++++++++++ internal/controllers/volume/status.go | 7 ++++++ .../tests/volume-dependency/00-assert.yaml | 15 +++++++++++++ .../00-create-resources-missing-deps.yaml | 13 +++++++++++ .../tests/volume-dependency/01-assert.yaml | 17 ++++++++++++++ .../01-create-dependencies.yaml | 15 +++++++++++++ .../tests/volume-dependency/02-assert.yaml | 4 ++++ .../02-delete-dependencies.yaml | 2 ++ .../03-delete-resources.yaml | 3 +++ .../volume/tests/volume-dependency/README.md | 6 +++-- .../api/v1alpha1/volumeresourcespec.go | 9 ++++++++ .../api/v1alpha1/volumeresourcestatus.go | 9 ++++++++ .../applyconfiguration/internal/internal.go | 6 +++++ website/docs/crd-reference.md | 2 ++ 20 files changed, 189 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/volume_types.go b/api/v1alpha1/volume_types.go index f50f83daa..49e2f3d06 100644 --- a/api/v1alpha1/volume_types.go +++ b/api/v1alpha1/volume_types.go @@ -56,6 +56,13 @@ type VolumeResourceSpec struct { // +listType=atomic // +optional Metadata []VolumeMetadata `json:"metadata,omitempty"` + + // imageRef is a reference to an ORC Image. If specified, creates a + // bootable volume from this image. The volume size must be >= the + // image's min_disk requirement. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable" + ImageRef *KubernetesNameRef `json:"imageRef,omitempty"` } // VolumeFilter defines an existing resource by its properties @@ -176,6 +183,11 @@ type VolumeResourceStatus struct { // +optional Bootable *bool `json:"bootable,omitempty"` + // imageID is the ID of the image this volume was created from, if any. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ImageID string `json:"imageID,omitempty"` + // encrypted denotes if the volume is encrypted. // +optional Encrypted *bool `json:"encrypted,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 74fd9dcdf..1024ea46a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5220,6 +5220,11 @@ func (in *VolumeResourceSpec) DeepCopyInto(out *VolumeResourceSpec) { *out = make([]VolumeMetadata, len(*in)) copy(*out, *in) } + if in.ImageRef != nil { + in, out := &in.ImageRef, &out.ImageRef + *out = new(KubernetesNameRef) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeResourceSpec. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3b90d275e..42f004b03 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -10090,6 +10090,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceSpec(ref }, }, }, + "imageRef": { + SchemaProps: spec.SchemaProps{ + Description: "imageRef is a reference to an ORC Image. If specified, creates a bootable volume from this image. The volume size must be >= the image's min_disk requirement.", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"size"}, }, @@ -10221,6 +10228,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceStatus(r Format: "", }, }, + "imageID": { + SchemaProps: spec.SchemaProps{ + Description: "imageID is the ID of the image this volume was created from, if any.", + Type: []string{"string"}, + Format: "", + }, + }, "encrypted": { SchemaProps: spec.SchemaProps{ Description: "encrypted denotes if the volume is encrypted.", diff --git a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml index aca503047..eeaf10a8b 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml @@ -173,6 +173,17 @@ spec: maxLength: 255 minLength: 1 type: string + imageRef: + description: |- + imageRef is a reference to an ORC Image. If specified, creates a + bootable volume from this image. The volume size must be >= the + image's min_disk requirement. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: imageRef is immutable + rule: self == oldSelf metadata: description: |- metadata key and value pairs to be associated with the volume. @@ -389,6 +400,11 @@ spec: description: host is the identifier of the host holding the volume. maxLength: 1024 type: string + imageID: + description: imageID is the ID of the image this volume was created + from, if any. + maxLength: 1024 + type: string metadata: description: metadata key and value pairs to be associated with the volume. diff --git a/config/samples/openstack_v1alpha1_volume.yaml b/config/samples/openstack_v1alpha1_volume.yaml index 08f5d608b..98cebd526 100644 --- a/config/samples/openstack_v1alpha1_volume.yaml +++ b/config/samples/openstack_v1alpha1_volume.yaml @@ -12,6 +12,7 @@ spec: description: Sample Volume size: 100 volumeTypeRef: my-volume-type + imageRef: ubuntu-2404 metadata: key1: value1 key2: value2 diff --git a/internal/controllers/volume/actuator.go b/internal/controllers/volume/actuator.go index 4e1e238f9..2fbde2b44 100644 --- a/internal/controllers/volume/actuator.go +++ b/internal/controllers/volume/actuator.go @@ -32,6 +32,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" ) @@ -165,6 +166,17 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject } } + // Resolve image dependency for bootable volumes + image, imageDepRS := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + resource.ImageRef, "Image", + func(dep *orcv1alpha1.Image) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(imageDepRS) + imageID := ptr.Deref(image.Status.ID, "") + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } @@ -181,6 +193,7 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject Metadata: metadata, VolumeType: volumetypeID, AvailabilityZone: resource.AvailabilityZone, + ImageID: imageID, } osResource, err := actuator.osClient.CreateVolume(ctx, createOpts) diff --git a/internal/controllers/volume/controller.go b/internal/controllers/volume/controller.go index 276c4236c..fb64c2c75 100644 --- a/internal/controllers/volume/controller.go +++ b/internal/controllers/volume/controller.go @@ -74,6 +74,19 @@ var volumetypeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.Vo finalizer, externalObjectFieldOwner, ) +// No deletion guard for image, because images can be safely deleted while +// referenced by a volume +var imageDependency = dependency.NewDependency[*orcv1alpha1.VolumeList, *orcv1alpha1.Image]( + "spec.resource.imageRef", + func(volume *orcv1alpha1.Volume) []string { + resource := volume.Spec.Resource + if resource == nil || resource.ImageRef == nil { + return nil + } + return []string{string(*resource.ImageRef)} + }, +) + // serverToVolumeMapFunc creates a mapping function that reconciles volumes when: // - a volume ID appears in server status but the volume doesn't have attachment info for that server // - a volume has attachment info for a server, but the server no longer lists that volume @@ -209,11 +222,19 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } + imageWatchEventHandler, err := imageDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). Watches(&orcv1alpha1.VolumeType{}, volumetypeWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VolumeType{})), ). + Watches(&orcv1alpha1.Image{}, imageWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Image{})), + ). Watches(&orcv1alpha1.Server{}, handler.EnqueueRequestsFromMapFunc(serverToVolumeMapFunc(ctx, k8sClient)), builder.WithPredicates(predicates.NewServerVolumesChanged(log)), ). @@ -221,6 +242,7 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c if err := errors.Join( volumetypeDependency.AddToManager(ctx, mgr), + imageDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/volume/status.go b/internal/controllers/volume/status.go index 064ef7575..96de129be 100644 --- a/internal/controllers/volume/status.go +++ b/internal/controllers/volume/status.go @@ -92,6 +92,13 @@ func (volumeStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes } } + // Extract image ID from volume_image_metadata if present. + // When a volume is created from an image, OpenStack stores the source + // image ID in the volume's metadata under "image_id". + if imageID, ok := osResource.VolumeImageMetadata["image_id"]; ok { + resourceStatus.WithImageID(imageID) + } + for k, v := range osResource.Metadata { resourceStatus.WithMetadata(orcapplyconfigv1alpha1.VolumeMetadataStatus(). WithName(k). diff --git a/internal/controllers/volume/tests/volume-dependency/00-assert.yaml b/internal/controllers/volume/tests/volume-dependency/00-assert.yaml index 92782c001..bd0a875de 100644 --- a/internal/controllers/volume/tests/volume-dependency/00-assert.yaml +++ b/internal/controllers/volume/tests/volume-dependency/00-assert.yaml @@ -28,3 +28,18 @@ status: message: Waiting for VolumeType/volume-dependency to be created status: "True" reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Volume +metadata: + name: volume-dependency-no-image +status: + conditions: + - type: Available + message: Waiting for Image/volume-dependency-image to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Image/volume-dependency-image to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml index ac339b291..bf7498567 100644 --- a/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml @@ -23,3 +23,16 @@ spec: managementPolicy: managed resource: size: 1 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Volume +metadata: + name: volume-dependency-no-image +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + size: 1 + imageRef: volume-dependency-image diff --git a/internal/controllers/volume/tests/volume-dependency/01-assert.yaml b/internal/controllers/volume/tests/volume-dependency/01-assert.yaml index df8013931..bc42ce16e 100644 --- a/internal/controllers/volume/tests/volume-dependency/01-assert.yaml +++ b/internal/controllers/volume/tests/volume-dependency/01-assert.yaml @@ -28,3 +28,20 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Volume +metadata: + name: volume-dependency-no-image +status: + resource: + size: 1 + status: available + bootable: true + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/volume/tests/volume-dependency/01-create-dependencies.yaml b/internal/controllers/volume/tests/volume-dependency/01-create-dependencies.yaml index 48f733a40..8614ae18b 100644 --- a/internal/controllers/volume/tests/volume-dependency/01-create-dependencies.yaml +++ b/internal/controllers/volume/tests/volume-dependency/01-create-dependencies.yaml @@ -15,3 +15,18 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Image +metadata: + name: volume-dependency-image +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + content: + diskFormat: raw + download: + url: https://github.com/k-orc/openstack-resource-controller/raw/690b760f49dfb61b173755e91cb51ed42472c7f3/internal/controllers/image/testdata/raw.img diff --git a/internal/controllers/volume/tests/volume-dependency/02-assert.yaml b/internal/controllers/volume/tests/volume-dependency/02-assert.yaml index 03226f163..b0ad37076 100644 --- a/internal/controllers/volume/tests/volume-dependency/02-assert.yaml +++ b/internal/controllers/volume/tests/volume-dependency/02-assert.yaml @@ -15,3 +15,7 @@ assertAll: - celExpr: "'openstack.k-orc.cloud/volume' in volumetype.metadata.finalizers" - celExpr: "secret.metadata.deletionTimestamp != 0" - celExpr: "'openstack.k-orc.cloud/volume' in secret.metadata.finalizers" +commands: +# Image is a creation dependency, so it should be deleted immediately +- script: "! kubectl get image volume-dependency-image --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/volume/tests/volume-dependency/02-delete-dependencies.yaml b/internal/controllers/volume/tests/volume-dependency/02-delete-dependencies.yaml index e1a57dbca..ed31865bd 100644 --- a/internal/controllers/volume/tests/volume-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/volume/tests/volume-dependency/02-delete-dependencies.yaml @@ -7,3 +7,5 @@ commands: namespaced: true - command: kubectl delete secret volume-dependency --wait=false namespaced: true + - command: kubectl delete image volume-dependency-image --wait=false + namespaced: true diff --git a/internal/controllers/volume/tests/volume-dependency/03-delete-resources.yaml b/internal/controllers/volume/tests/volume-dependency/03-delete-resources.yaml index 029d18239..d6522eb2b 100644 --- a/internal/controllers/volume/tests/volume-dependency/03-delete-resources.yaml +++ b/internal/controllers/volume/tests/volume-dependency/03-delete-resources.yaml @@ -8,3 +8,6 @@ delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Volume name: volume-dependency-no-volumetype +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Volume + name: volume-dependency-no-image diff --git a/internal/controllers/volume/tests/volume-dependency/README.md b/internal/controllers/volume/tests/volume-dependency/README.md index 1cb2029b1..b9d398843 100644 --- a/internal/controllers/volume/tests/volume-dependency/README.md +++ b/internal/controllers/volume/tests/volume-dependency/README.md @@ -10,11 +10,13 @@ Create the missing dependencies and make and verify all the Volumes are availabl ## Step 02 -Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. +Delete all the dependencies and check: +- VolumeType and Secret have finalizers preventing deletion (hard dependencies) +- Image is deleted immediately (soft dependency - no finalizer) ## Step 03 -Delete the Volumes and validate that all resources are gone. +Delete the Volumes and validate that VolumeType and Secret are now gone. ## Reference diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go index efa5c19ca..d6c7f2f04 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go @@ -31,6 +31,7 @@ type VolumeResourceSpecApplyConfiguration struct { VolumeTypeRef *apiv1alpha1.KubernetesNameRef `json:"volumeTypeRef,omitempty"` AvailabilityZone *string `json:"availabilityZone,omitempty"` Metadata []VolumeMetadataApplyConfiguration `json:"metadata,omitempty"` + ImageRef *apiv1alpha1.KubernetesNameRef `json:"imageRef,omitempty"` } // VolumeResourceSpecApplyConfiguration constructs a declarative configuration of the VolumeResourceSpec type for use with @@ -91,3 +92,11 @@ func (b *VolumeResourceSpecApplyConfiguration) WithMetadata(values ...*VolumeMet } return b } + +// WithImageRef sets the ImageRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ImageRef field is set to the value of the last call. +func (b *VolumeResourceSpecApplyConfiguration) WithImageRef(value apiv1alpha1.KubernetesNameRef) *VolumeResourceSpecApplyConfiguration { + b.ImageRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go index 1ac93544c..a9eb7c404 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go @@ -38,6 +38,7 @@ type VolumeResourceStatusApplyConfiguration struct { Metadata []VolumeMetadataStatusApplyConfiguration `json:"metadata,omitempty"` UserID *string `json:"userID,omitempty"` Bootable *bool `json:"bootable,omitempty"` + ImageID *string `json:"imageID,omitempty"` Encrypted *bool `json:"encrypted,omitempty"` ReplicationStatus *string `json:"replicationStatus,omitempty"` ConsistencyGroupID *string `json:"consistencyGroupID,omitempty"` @@ -168,6 +169,14 @@ func (b *VolumeResourceStatusApplyConfiguration) WithBootable(value bool) *Volum return b } +// WithImageID sets the ImageID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ImageID field is set to the value of the last call. +func (b *VolumeResourceStatusApplyConfiguration) WithImageID(value string) *VolumeResourceStatusApplyConfiguration { + b.ImageID = &value + return b +} + // WithEncrypted sets the Encrypted field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Encrypted field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index abaeca27f..dfb83d47f 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3001,6 +3001,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: description type: scalar: string + - name: imageRef + type: + scalar: string - name: metadata type: list: @@ -3049,6 +3052,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: host type: scalar: string + - name: imageID + type: + scalar: string - name: metadata type: list: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 32018f962..f1664a185 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -3972,6 +3972,7 @@ _Appears in:_ | `volumeTypeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeTypeRef is a reference to the ORC VolumeType which this resource is associated with. | | MaxLength: 253
MinLength: 1
| | `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the volume. | | MaxLength: 255
| | `metadata` _[VolumeMetadata](#volumemetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
| +| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef is a reference to an ORC Image. If specified, creates a
bootable volume from this image. The volume size must be >= the
image's min_disk requirement. | | MaxLength: 253
MinLength: 1
| #### VolumeResourceStatus @@ -4000,6 +4001,7 @@ _Appears in:_ | `metadata` _[VolumeMetadataStatus](#volumemetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
| | `userID` _string_ | userID is the ID of the user who created the volume. | | MaxLength: 1024
| | `bootable` _boolean_ | bootable indicates whether this is a bootable volume. | | | +| `imageID` _string_ | imageID is the ID of the image this volume was created from, if any. | | MaxLength: 1024
| | `encrypted` _boolean_ | encrypted denotes if the volume is encrypted. | | | | `replicationStatus` _string_ | replicationStatus is the status of replication. | | MaxLength: 1024
| | `consistencyGroupID` _string_ | consistencyGroupID is the consistency group ID. | | MaxLength: 1024
| From 8da7243a0e9961e4b1be11a109d2b380c6648fee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:43:56 +0000 Subject: [PATCH 024/121] :seedling:(deps): Bump actions/checkout in the all-github-actions group Bumps the all-github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v6.0.1...v6.0.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/container_image.yaml | 2 +- .github/workflows/e2e.yaml | 2 +- .github/workflows/ensure-labels.yaml | 2 +- .github/workflows/generate.yaml | 2 +- .github/workflows/go-lint.yaml | 2 +- .github/workflows/label-pr.yaml | 2 +- .github/workflows/pr-dependabot.yaml | 2 +- .github/workflows/release_image.yaml | 2 +- .github/workflows/unit.yml | 2 +- .github/workflows/website.yaml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index c315a3e50..8a85be043 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 with: # Required for git describe to generate correct output for populating # build variables diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index da07b7eee..bee7d240f 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-${{ matrix.ubuntu_version }} steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: Deploy devstack uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index d4cea7346..21a654236 100644 --- a/.github/workflows/ensure-labels.yaml +++ b/.github/workflows/ensure-labels.yaml @@ -13,7 +13,7 @@ jobs: ensure: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - uses: micnncim/action-label-syncer@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index c87b2dbfb..9bb3e7515 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - run: | make generate diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index 37236a59e..1ab6ece29 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: Calculate go version id: vars diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 1df1d4e5d..9e796850f 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -13,7 +13,7 @@ jobs: semver: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index a111be029..e7c805ec0 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # tag=v4.2.2 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # tag=v4.2.2 - name: Calculate go version id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index 98c855442..791f42d5e 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 with: # Required for git describe to generate correct output for populating # build variables diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index b74657d8e..0f6481873 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -17,7 +17,7 @@ jobs: - '1' steps: - - uses: actions/checkout@v6.0.1 + - uses: actions/checkout@v6.0.2 - name: Calculate go version id: vars diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 5ae350748..0ca6fd644 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -17,7 +17,7 @@ jobs: name: Publish to Cloudflare Pages steps: - name: Checkout - uses: actions/checkout@v6.0.1 + uses: actions/checkout@v6.0.2 - name: Pip install run: pip install -Ur website/requirements.txt diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index abf76392d..0b21bfaae 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # tag=v4.2.2 + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # tag=v4.2.2 with: ref: ${{ matrix.branch }} - name: Calculate go version From a24560bd020b7d3fd8726574d797bbd7183fca93 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Tue, 20 Jan 2026 16:11:58 +0100 Subject: [PATCH 025/121] Fix SecurityGroup availability status by counting security group rules SecurityGroups now count their specified rules and compare them to the number of rules in their ORC status and the openstack resource. As the security group rules are only ever part of one security group, this should be enough to reliably determine if all rules have been successfully created. On-behalf-of: SAP nils.gondermann@sap.com --- .../controllers/securitygroup/actuator.go | 9 ++++++--- internal/controllers/securitygroup/status.go | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index 703f25c7c..09dbc5557 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "iter" + "time" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" @@ -50,6 +51,11 @@ type ( securityGroupIterator = iter.Seq2[*osResourceT, error] ) +const ( + // The frequency to poll when waiting for the resource to become available + securityGroupAvailablePollingPeriod = 15 * time.Second +) + type securityGroupActuator struct { osClient osclients.NetworkClient k8sClient client.Client @@ -134,9 +140,6 @@ func (actuator securityGroupActuator) CreateResource(ctx context.Context, obj *o ProjectID: projectID, } - // FIXME(mandre) The security group inherits the default security group - // rules. This could be a problem when we implement `update` if ORC - // does not takes these rules into account. osResource, err := actuator.osClient.CreateSecGroup(ctx, &createOpts) if err != nil { // We should require the spec to be updated before retrying a create which returned a conflict diff --git a/internal/controllers/securitygroup/status.go b/internal/controllers/securitygroup/status.go index 94a83c8f7..90e172d45 100644 --- a/internal/controllers/securitygroup/status.go +++ b/internal/controllers/securitygroup/status.go @@ -45,7 +45,24 @@ func (securityGroupStatusWriter) ResourceAvailableStatus(orcObject orcObjectPT, } } - // SecurityGroup is available as soon as it exists + resourceSpec := orcObject.Spec.Resource + if resourceSpec != nil && resourceSpec.Rules != nil { + // Make sure specified security group rules exist in resource + + resourceStatus := orcObject.Status.Resource + if resourceStatus == nil || resourceStatus.Rules == nil { + return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, securityGroupAvailablePollingPeriod) + } + + if len(resourceSpec.Rules) != len(resourceStatus.Rules) { + return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, securityGroupAvailablePollingPeriod) + } + + if len(resourceSpec.Rules) != len(osResource.Rules) { + return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, securityGroupAvailablePollingPeriod) + } + } + return metav1.ConditionTrue, nil } From dfede6b1f972551aad0ced4a4a1843b5209ff673 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Mon, 26 Jan 2026 15:50:11 +0100 Subject: [PATCH 026/121] Fix RouterInterface status when waiting for routerRef The RouterInterface controller is special in that it uses a custom reconciler. The reconcile method is called with a Request containing a router reference, since RouterInterfaces require an underlying Router. The custom reconciler does not take into account what happens when the underlying Router, defined by the routerRef field in the RouterInterface spec, does not (yet) exist: It returns an empty Result with no error. But the most important part is that it does not create an update for the RouterInterface in that case which results in non-existent status conditions. This fix will catch the non-existent router case and update any affected RouterInterfaces with a WaitingOnObject status. --- .../controllers/routerinterface/reconcile.go | 33 ++++++++++++++++++- .../routerinterface-dependency/00-assert.yaml | 21 ++++++------ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/internal/controllers/routerinterface/reconcile.go b/internal/controllers/routerinterface/reconcile.go index 2aba2f001..6b8daeb03 100644 --- a/internal/controllers/routerinterface/reconcile.go +++ b/internal/controllers/routerinterface/reconcile.go @@ -48,7 +48,38 @@ func (r *orcRouterInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.R router := &orcv1alpha1.Router{} if err := r.client.Get(ctx, req.NamespacedName, router); err != nil { if apierrors.IsNotFound(err) { - return ctrl.Result{}, nil + // The router does not exist (yet). We still need to update the status + // on all RouterInterfaces that are associated with that router + + // Creating a dummy router struct with namespace and name will be enough to + // retrieve all defined RouterInterfaces for that to-be-created router + router.Name = req.Name + router.Namespace = req.Namespace + routerInterfaces, err := routerDependency.GetObjectsForDependency(ctx, r.client, router) + + if err != nil { + return ctrl.Result{}, fmt.Errorf("fetching router interfaces: %w", err) + } + + if len(routerInterfaces) == 0 { + return ctrl.Result{}, nil + } + + var osResource *osclients.PortExt + + var reconcileStatus progress.ReconcileStatus + for i := range routerInterfaces { + routerInterface := &routerInterfaces[i] + log = log.WithValues("name", routerInterface.Name) + + var ifReconcileStatus progress.ReconcileStatus + ifReconcileStatus = progress.WaitingOnObject("Router", req.Name, progress.WaitingOnCreation) + ifReconcileStatus = ifReconcileStatus.WithReconcileStatus(r.updateStatus(ctx, routerInterface, osResource, ifReconcileStatus)) + + reconcileStatus = reconcileStatus.WithReconcileStatus(ifReconcileStatus) + } + + return reconcileStatus.Return(log) } return ctrl.Result{}, err } diff --git a/internal/controllers/routerinterface/tests/routerinterface-dependency/00-assert.yaml b/internal/controllers/routerinterface/tests/routerinterface-dependency/00-assert.yaml index 06aed6c4f..3a1047ca5 100644 --- a/internal/controllers/routerinterface/tests/routerinterface-dependency/00-assert.yaml +++ b/internal/controllers/routerinterface/tests/routerinterface-dependency/00-assert.yaml @@ -3,17 +3,16 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RouterInterface metadata: name: routerinterface-dependency-no-router -# FIXME: https://github.com/k-orc/openstack-resource-controller/issues/314 -# status: -# conditions: -# - type: Available -# message: Waiting for Router/routerinterface-dependency-pending to be created -# status: "False" -# reason: Progressing -# - type: Progressing -# message: Waiting for Router/routerinterface-dependency-pending to be created -# status: "True" -# reason: Progressing +status: + conditions: + - type: Available + message: Waiting for Router/routerinterface-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Router/routerinterface-dependency-pending to be created + status: "True" + reason: Progressing --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RouterInterface From 954538916dee766aed0a20ec8e339ca8d7b1abbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 2 Feb 2026 10:48:37 +0100 Subject: [PATCH 027/121] Bump go to v1.24.12 This fixes 2 CVEs: - https://pkg.go.dev/vuln/GO-2026-4341 - https://pkg.go.dev/vuln/GO-2026-4340 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 57bcd42fb..f65f038a7 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ BUNDLE_IMG ?= bundle:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.29.0 TRIVY_VERSION = 0.49.1 -GO_VERSION ?= 1.24.11 +GO_VERSION ?= 1.24.12 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) From e809b572d11e62c655c878bb524c89bf9f1c86e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:15:37 +0000 Subject: [PATCH 028/121] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 2 updates Bumps the all-go-mod-patch-and-minor group with 2 updates in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega). Updates `github.com/onsi/ginkgo/v2` from 2.27.5 to 2.28.1 - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.27.5...v2.28.1) Updates `github.com/onsi/gomega` from 1.39.0 to 1.39.1 - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.39.0...v1.39.1) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.28.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor - dependency-name: github.com/onsi/gomega dependency-version: 1.39.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 57b18fb16..fdcdbb00d 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/go-logr/logr v1.4.3 github.com/gophercloud/gophercloud/v2 v2.10.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.27.5 - github.com/onsi/gomega v1.39.0 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 golang.org/x/text v0.33.0 @@ -50,7 +50,7 @@ require ( github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -84,14 +84,14 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.40.0 // indirect + golang.org/x/tools v0.41.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index 8695293a5..337539971 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gophercloud/gophercloud/v2 v2.10.0 h1:NRadC0aHNvy4iMoFXj5AFiPmut/Sj3hAPAo9B59VMGc= @@ -117,10 +117,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE= -github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= -github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -207,14 +207,14 @@ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -225,10 +225,10 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= @@ -239,8 +239,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= From 7cd66df7e91f320536e751206650e7fbb21193ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:16:12 +0000 Subject: [PATCH 029/121] :seedling:(deps): Bump actions/cache in the all-github-actions group Bumps the all-github-actions group with 1 update: [actions/cache](https://github.com/actions/cache). Updates `actions/cache` from 5.0.2 to 5.0.3 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/8b402f58fbc84540c8b491a91e594a4576fec3d7...cdf6c1fa76f9f475f3d7449005a359c84ca0f306) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/pr-dependabot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index e7c805ec0..6b35cdca2 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -27,7 +27,7 @@ jobs: uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 with: go-version: ${{ steps.vars.outputs.go_version }} - - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # tag=v5.0.2 + - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # tag=v5.0.3 name: Restore go cache with: path: | From 49ad3f2b41c41699349c57d60adb059a5455f280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 2 Feb 2026 15:30:02 +0100 Subject: [PATCH 030/121] Add lightweight enhancement proposal process Introduce a process for contributors to propose significant enhancements, inspired by Kubernetes KEPs but tailored to ORC's scope. - Add enhancements/ directory with process README and template - Reference enhancement process from README, website, and feature request template - Support single-file enhancements or directories for proposals with supporting files --- .github/ISSUE_TEMPLATE/feature_request.yaml | 4 + .github/labels.yaml | 3 + README.md | 3 + enhancements/README.md | 104 ++++++++++++++++++++ enhancements/TEMPLATE.md | 102 +++++++++++++++++++ website/docs/index.md | 4 + 6 files changed, 220 insertions(+) create mode 100644 enhancements/README.md create mode 100644 enhancements/TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 59eec8232..7162b2299 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -6,6 +6,10 @@ body: attributes: value: | Thanks for taking the time to fill out this feature request! + + **Note:** For significant new features or architectural changes, consider writing an + [enhancement proposal](https://github.com/k-orc/openstack-resource-controller/tree/main/enhancements) + instead of or in addition to this issue. - type: textarea id: request attributes: diff --git a/.github/labels.yaml b/.github/labels.yaml index 7541d0f20..d53fa110a 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -26,3 +26,6 @@ - color: 'C2E0C6' description: Documentation name: docs +- color: 'A2EEEF' + description: Enhancement proposal + name: enhancement diff --git a/README.md b/README.md index 17597fdbb..8ced52b8b 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ We welcome contributions of all kinds! Whether you’re fixing bugs, adding new * Make your changes and test thoroughly. * Submit a pull request with a clear description of your changes. +For significant new features or architectural changes, please review our +[enhancement proposal process](enhancements/README.md) before starting work. + If you're unsure where to start, check out the [open issues](https://github.com/k-orc/openstack-resource-controller/issues) and feel free to ask questions or propose ideas! diff --git a/enhancements/README.md b/enhancements/README.md new file mode 100644 index 000000000..a5013ceb2 --- /dev/null +++ b/enhancements/README.md @@ -0,0 +1,104 @@ +# ORC Enhancement Process + +This document describes the process for proposing significant changes to ORC. +The process is intentionally lightweight, inspired by the [Kubernetes +Enhancement Proposal (KEP)][kep] process but tailored to ORC's scope and +community size. + +[kep]: https://github.com/kubernetes/enhancements/tree/master/keps + +## When to Write an Enhancement + +Write an enhancement proposal when you want to: + +- Add a significant new feature or capability +- Make breaking changes to existing APIs +- Deprecate or remove functionality +- Make cross-cutting architectural changes +- Change behavior that users depend on + +You do **not** need an enhancement for: + +- Bug fixes +- Small improvements or refactoring +- Documentation updates +- Adding support for additional OpenStack resource fields +- Test improvements + +When in doubt, open a GitHub issue first to discuss whether an enhancement +proposal is needed. + +## Enhancement Lifecycle + +Enhancements move through the following statuses: + +| Status | Description | +|--------|-------------| +| `implementable` | The enhancement has been approved and is ready for implementation. | +| `implemented` | The enhancement has been fully implemented and merged. | +| `withdrawn` | The enhancement is no longer being pursued. | + +## How to Submit an Enhancement + +1. **Copy the template** from [TEMPLATE.md](TEMPLATE.md) to a new file named + after your feature: + ``` + enhancements/your-feature-name.md + ``` + + If your enhancement requires supporting files (images, diagrams), create a + directory instead: + ``` + enhancements/your-feature-name/ + ├── your-feature-name.md + └── diagram.png + ``` + +2. **Fill out the template** with your proposal details. + +3. **Open a pull request** with your enhancement proposal. Use a descriptive + title like: `Enhancement: Add support for feature X` + +4. **Iterate based on feedback**. Discussion happens on the PR. + +5. **Create a tracking issue** once the enhancement is merged. Label the issue + with `enhancement` and link it in your enhancement's metadata table. + +## Review Process + +- Any community member can propose an enhancement +- Maintainers review proposals and provide feedback on the PR +- Enhancements are approved using lazy consensus: if no maintainer has objected + after a reasonable review period (typically one week), the enhancement can be + merged +- The enhancement author is typically expected to drive implementation, though + others may volunteer + +## Directory Structure + +``` +enhancements/ +├── README.md # This document +├── TEMPLATE.md # Template for new enhancements +├── your-feature-name.md # Simple enhancement (single file) +└── complex-feature/ # Enhancement with supporting files + ├── complex-feature.md + └── architecture.png +``` + +## Tips for Writing Good Enhancements + +1. **Be concise but complete**. Include enough detail for reviewers to + understand the proposal without unnecessary verbosity. + +2. **Focus on the "why"**. Motivation is often more important than + implementation details. + +3. **Think about edge cases**. The Risks and Edge Cases section is where you + demonstrate you've thought through the implications. + +4. **Consider alternatives**. Showing that you've evaluated other approaches + strengthens your proposal. + +5. **Keep it updated**. As implementation progresses, update the Implementation + History section. diff --git a/enhancements/TEMPLATE.md b/enhancements/TEMPLATE.md new file mode 100644 index 000000000..205479cfa --- /dev/null +++ b/enhancements/TEMPLATE.md @@ -0,0 +1,102 @@ +# Enhancement: Your Feature Title + + + +| Field | Value | +|-------|-------| +| **Status** | implementable | +| **Author(s)** | @your-github-username | +| **Created** | YYYY-MM-DD | +| **Last Updated** | YYYY-MM-DD | +| **Tracking Issue** | TBD | + +## Summary + + + +## Motivation + + + +## Goals + + + +## Non-Goals + + + +## Proposal + + + +## Risks and Edge Cases + + + +## Alternatives Considered + + + +## Implementation History + + + +- YYYY-MM-DD: Enhancement proposed diff --git a/website/docs/index.md b/website/docs/index.md index 95edde167..94bf015fb 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -76,6 +76,10 @@ We welcome contributions of all kinds! Whether you're fixing bugs, adding new fe * Make your changes and test thoroughly. * Submit a pull request with a clear description of your changes. +For significant new features or architectural changes, please review our +[enhancement proposal process](https://github.com/k-orc/openstack-resource-controller/tree/main/enhancements) +before starting work. + If you're unsure where to start, check out the [open issues](https://github.com/k-orc/openstack-resource-controller/issues) and feel free to ask questions or propose ideas! From f3a38b37a92a7c892aaba699973971af6acb17bb Mon Sep 17 00:00:00 2001 From: Gondermann Date: Tue, 3 Feb 2026 15:43:45 +0100 Subject: [PATCH 031/121] Add availabilty check for SecurityGroups in Port controller On-behalf-of: SAP nils.gondermann@sap.com --- internal/controllers/port/actuator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index f4890c03a..fad8acd7c 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -145,7 +145,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha ) secGroupMap, secGroupDepRS := securityGroupDependency.GetDependencies( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.SecurityGroup) bool { - return dep.Status.ID != nil + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) reconcileStatus := progress.NewReconcileStatus(). @@ -323,7 +323,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT secGroupMap, secGroupDepRS := securityGroupDependency.GetDependencies( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.SecurityGroup) bool { - return dep.Status.ID != nil + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) From c204df00434b89d4d67585b3f48c495e47be1c1b Mon Sep 17 00:00:00 2001 From: Mohammed Al-Dokimi Date: Fri, 9 Jan 2026 12:59:49 +0100 Subject: [PATCH 032/121] Generate scaffolding for trunk controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated with: ❯ go run ./cmd/scaffold-controller \ -interactive=false \ -kind Trunk \ -gophercloud-client NewNetworkV2 \ -gophercloud-module github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks \ -import-dependency Port \ -import-dependency Project \ -optional-create-dependency Project \ -required-create-dependency Port --- api/v1alpha1/trunk_types.go | 102 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 80 +++++ cmd/models-schema/zz_generated.openapi.go | 127 ++++++++ config/rbac/role.yaml | 2 + config/samples/openstack_v1alpha1_trunk.yaml | 14 + internal/controllers/trunk/actuator.go | 293 ++++++++++++++++++ internal/controllers/trunk/actuator_test.go | 119 +++++++ internal/controllers/trunk/controller.go | 156 ++++++++++ internal/controllers/trunk/status.go | 65 ++++ .../tests/trunk-create-full/00-assert.yaml | 38 +++ .../trunk-create-full/00-create-resource.yaml | 43 +++ .../tests/trunk-create-full/00-secret.yaml | 6 + .../trunk/tests/trunk-create-full/README.md | 11 + .../tests/trunk-create-minimal/00-assert.yaml | 32 ++ .../00-create-resource.yaml | 28 ++ .../tests/trunk-create-minimal/00-secret.yaml | 6 + .../tests/trunk-create-minimal/01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../tests/trunk-create-minimal/README.md | 15 + .../tests/trunk-dependency/00-assert.yaml | 45 +++ .../00-create-resources-missing-deps.yaml | 56 ++++ .../tests/trunk-dependency/00-secret.yaml | 6 + .../tests/trunk-dependency/01-assert.yaml | 45 +++ .../01-create-dependencies.yaml | 32 ++ .../tests/trunk-dependency/02-assert.yaml | 23 ++ .../02-delete-dependencies.yaml | 11 + .../tests/trunk-dependency/03-assert.yaml | 11 + .../trunk-dependency/03-delete-resources.yaml | 13 + .../trunk/tests/trunk-dependency/README.md | 21 ++ .../trunk-import-dependency/00-assert.yaml | 19 ++ .../00-import-resource.yaml | 40 +++ .../trunk-import-dependency/00-secret.yaml | 6 + .../trunk-import-dependency/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 56 ++++ .../trunk-import-dependency/02-assert.yaml | 39 +++ .../02-create-resource.yaml | 55 ++++ .../trunk-import-dependency/03-assert.yaml | 8 + .../03-delete-import-dependencies.yaml | 9 + .../trunk-import-dependency/04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../tests/trunk-import-dependency/README.md | 29 ++ .../tests/trunk-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 43 +++ .../tests/trunk-import-error/00-secret.yaml | 6 + .../tests/trunk-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../trunk/tests/trunk-import-error/README.md | 13 + .../trunk/tests/trunk-import/00-assert.yaml | 15 + .../trunk-import/00-import-resource.yaml | 15 + .../trunk/tests/trunk-import/00-secret.yaml | 6 + .../trunk/tests/trunk-import/01-assert.yaml | 34 ++ .../trunk-import/01-create-trap-resource.yaml | 31 ++ .../trunk/tests/trunk-import/02-assert.yaml | 33 ++ .../trunk-import/02-create-resource.yaml | 28 ++ .../trunk/tests/trunk-import/README.md | 18 ++ .../trunk/tests/trunk-update/00-assert.yaml | 26 ++ .../trunk-update/00-minimal-resource.yaml | 28 ++ .../tests/trunk-update/00-prerequisites.yaml | 6 + .../trunk/tests/trunk-update/01-assert.yaml | 17 + .../trunk-update/01-updated-resource.yaml | 10 + .../trunk/tests/trunk-update/02-assert.yaml | 26 ++ .../trunk-update/02-reverted-resource.yaml | 7 + .../trunk/tests/trunk-update/README.md | 17 + internal/osclients/trunk.go | 104 +++++++ website/docs/crd-reference.md | 10 + 65 files changed, 2277 insertions(+) create mode 100644 api/v1alpha1/trunk_types.go create mode 100644 config/samples/openstack_v1alpha1_trunk.yaml create mode 100644 internal/controllers/trunk/actuator.go create mode 100644 internal/controllers/trunk/actuator_test.go create mode 100644 internal/controllers/trunk/controller.go create mode 100644 internal/controllers/trunk/status.go create mode 100644 internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-full/00-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-full/README.md create mode 100644 internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-minimal/00-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-minimal/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-minimal/README.md create mode 100644 internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/00-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/trunk/tests/trunk-dependency/README.md create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/00-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/02-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/03-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/04-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-dependency/README.md create mode 100644 internal/controllers/trunk/tests/trunk-import-error/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-error/00-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-error/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-error/01-import-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import-error/README.md create mode 100644 internal/controllers/trunk/tests/trunk-import/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/00-secret.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/02-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-import/README.md create mode 100644 internal/controllers/trunk/tests/trunk-update/00-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/00-prerequisites.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/02-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/02-reverted-resource.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/README.md create mode 100644 internal/osclients/trunk.go diff --git a/api/v1alpha1/trunk_types.go b/api/v1alpha1/trunk_types.go new file mode 100644 index 000000000..4f834f8c9 --- /dev/null +++ b/api/v1alpha1/trunk_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 + +// TrunkResourceSpec contains the desired state of the resource. +type TrunkResourceSpec 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"` + + // portRef is a reference to the ORC Port which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="portRef is immutable" + PortRef KubernetesNameRef `json:"portRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the CreateOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks + // + // 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"` +} + +// TrunkFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type TrunkFilter 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"` + + // portRef is a reference to the ORC Port which this resource is associated with. + // +optional + PortRef *KubernetesNameRef `json:"portRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ListOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks +} + +// TrunkResourceStatus represents the observed state of the resource. +type TrunkResourceStatus 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"` + + // portID is the ID of the Port to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + PortID string `json:"portID,omitempty"` + + // projectID is the ID of the Project to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the Trunk structure from + // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1024ea46a..11968bc6e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5014,6 +5014,86 @@ func (in *SubnetStatus) DeepCopy() *SubnetStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkFilter) DeepCopyInto(out *TrunkFilter) { + *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.PortRef != nil { + in, out := &in.PortRef, &out.PortRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkFilter. +func (in *TrunkFilter) DeepCopy() *TrunkFilter { + if in == nil { + return nil + } + out := new(TrunkFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkResourceSpec) DeepCopyInto(out *TrunkResourceSpec) { + *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 + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkResourceSpec. +func (in *TrunkResourceSpec) DeepCopy() *TrunkResourceSpec { + if in == nil { + return nil + } + out := new(TrunkResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkResourceStatus) DeepCopyInto(out *TrunkResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkResourceStatus. +func (in *TrunkResourceStatus) DeepCopy() *TrunkResourceStatus { + if in == nil { + return nil + } + out := new(TrunkResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UserDataSpec) DeepCopyInto(out *UserDataSpec) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 42f004b03..44fa7307a 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -201,6 +201,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "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), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Volume": schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeAttachmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeAttachmentStatus(ref), @@ -9741,6 +9744,130 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkFilter 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: "", + }, + }, + "portRef": { + SchemaProps: spec.SchemaProps{ + Description: "portRef is a reference to the ORC Port which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkResourceSpec 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: "", + }, + }, + "portRef": { + SchemaProps: spec.SchemaProps{ + Description: "portRef is a reference to the ORC Port which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"portRef"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkResourceStatus 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: "", + }, + }, + "portID": { + SchemaProps: spec.SchemaProps{ + Description: "portID is the ID of the Port to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the ID of the Project to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5a0a7443b..5545c52a6 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -34,6 +34,7 @@ rules: - servers - services - subnets + - trunks - volumes - volumetypes verbs: @@ -64,6 +65,7 @@ rules: - servers/status - services/status - subnets/status + - trunks/status - volumes/status - volumetypes/status verbs: diff --git a/config/samples/openstack_v1alpha1_trunk.yaml b/config/samples/openstack_v1alpha1_trunk.yaml new file mode 100644 index 000000000..cb038421b --- /dev/null +++ b/config/samples/openstack_v1alpha1_trunk.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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 Trunk + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/trunk/actuator.go b/internal/controllers/trunk/actuator.go new file mode 100644 index 000000000..facc802e1 --- /dev/null +++ b/internal/controllers/trunk/actuator.go @@ -0,0 +1,293 @@ +/* +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 trunk + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks" + 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 = trunks.Trunk + + 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 trunkActuator struct { + osClient osclients.TrunkClient + k8sClient client.Client +} + +var _ createResourceActuator = trunkActuator{} +var _ deleteResourceActuator = trunkActuator{} + +func (trunkActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator trunkActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetTrunk(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator trunkActuator) 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 := trunks.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListTrunks(ctx, listOpts), true +} + +func (actuator trunkActuator) 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 + + port, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.PortRef, "Port", + func(dep *orcv1alpha1.Port) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := trunks.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + PortID: ptr.Deref(port.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListTrunks(ctx, listOpts), reconcileStatus +} + +func (actuator trunkActuator) 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 portID string + port, portDepRS := portDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS) + if port != nil { + portID = ptr.Deref(port.Status.ID, "") + } + + var projectID string + if resource.ProjectRef != nil { + project, projectDepRS := projectDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) + if project != nil { + projectID = ptr.Deref(project.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := trunks.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + PortID: portID, + ProjectID: projectID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateTrunk(ctx, createOpts) + if err != nil { + // We should require the spec to be updated before retrying a create which returned a conflict + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + return osResource, nil +} + +func (actuator trunkActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteTrunk(ctx, resource.ID)) +} + +func (actuator trunkActuator) 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 := trunks.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.UpdateTrunk(ctx, osResource.ID, updateOpts) + + // We should require the spec to be updated before retrying an update which returned a conflict + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts trunks.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToTrunkUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["trunk"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *trunks.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *trunks.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator trunkActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type trunkHelperFactory struct{} + +var _ helperFactory = trunkHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.Trunk, controller interfaces.ResourceController) (trunkActuator, 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 trunkActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return trunkActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewTrunkClient() + if err != nil { + return trunkActuator{}, progress.WrapError(err) + } + + return trunkActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (trunkHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return trunkAdapter{obj} +} + +func (trunkHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (trunkHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/trunk/actuator_test.go b/internal/controllers/trunk/actuator_test.go new file mode 100644 index 000000000..66e8d3a1a --- /dev/null +++ b/internal/controllers/trunk/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 trunk + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks" + 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 trunks.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: trunks.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: trunks.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.Trunk{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.TrunkSpec{ + Resource: &orcv1alpha1.TrunkResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := trunks.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.TrunkResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := trunks.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/trunk/controller.go b/internal/controllers/trunk/controller.go new file mode 100644 index 000000000..8cd733ff5 --- /dev/null +++ b/internal/controllers/trunk/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 trunk + +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 = "trunk" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=trunks,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=trunks/status,verbs=get;update;patch + +type trunkReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return trunkReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (trunkReconcilerConstructor) GetName() string { + return controllerName +} + +var portDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.TrunkList, *orcv1alpha1.Port]( + "spec.resource.portRef", + func(trunk *orcv1alpha1.Trunk) []string { + resource := trunk.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.PortRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.TrunkList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(trunk *orcv1alpha1.Trunk) []string { + resource := trunk.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var portImportDependency = dependency.NewDependency[*orcv1alpha1.TrunkList, *orcv1alpha1.Port]( + "spec.import.filter.portRef", + func(trunk *orcv1alpha1.Trunk) []string { + resource := trunk.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.PortRef == nil { + return nil + } + return []string{string(*resource.Filter.PortRef)} + }, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.TrunkList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(trunk *orcv1alpha1.Trunk) []string { + resource := trunk.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + portWatchEventHandler, err := portDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + portImportWatchEventHandler, err := portImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Port{}, portWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Port{})), + ). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Port{}, portImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Port{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + For(&orcv1alpha1.Trunk{}) + + if err := errors.Join( + portDependency.AddToManager(ctx, mgr), + projectDependency.AddToManager(ctx, mgr), + portImportDependency.AddToManager(ctx, mgr), + projectImportDependency.AddToManager(ctx, mgr), + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, trunkHelperFactory{}, trunkStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/trunk/status.go b/internal/controllers/trunk/status.go new file mode 100644 index 000000000..eb11a91a7 --- /dev/null +++ b/internal/controllers/trunk/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 trunk + +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 trunkStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.TrunkApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.TrunkStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.Trunk, *osResourceT, *objectApplyT, *statusApplyT] = trunkStatusWriter{} + +func (trunkStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.Trunk(name, namespace) +} + +func (trunkStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Trunk, 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 (trunkStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.TrunkResourceStatus(). + WithPortID(osResource.PortID). + WithProjectID(osResource.ProjectID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the TrunkResourceStatus 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/trunk/tests/trunk-create-full/00-assert.yaml b/internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml new file mode 100644 index 000000000..c3a7df9c9 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-create-full +status: + resource: + name: trunk-create-full-override + description: Trunk 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: Trunk + name: trunk-create-full + ref: trunk + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-create-full + ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: trunk-create-full + ref: project +assertAll: + - celExpr: "trunk.status.id != ''" + - celExpr: "trunk.status.resource.portID == port.status.id" + - celExpr: "trunk.status.resource.projectID == project.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml b/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml new file mode 100644 index 000000000..bf1cf03da --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: trunk-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-create-full-override + description: Trunk from "create full" test + portRef: trunk-create-full + projectRef: trunk-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/trunk/tests/trunk-create-full/00-secret.yaml b/internal/controllers/trunk/tests/trunk-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-create-full/README.md b/internal/controllers/trunk/tests/trunk-create-full/README.md new file mode 100644 index 000000000..45eabc565 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-full/README.md @@ -0,0 +1,11 @@ +# Create a Trunk with all the options + +## Step 00 + +Create a Trunk 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/trunk/tests/trunk-create-minimal/00-assert.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml new file mode 100644 index 000000000..029262124 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-create-minimal +status: + resource: + name: trunk-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: Trunk + name: trunk-create-minimal + ref: trunk + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-create-minimal + ref: port +assertAll: + - celExpr: "trunk.status.id != ''" + - celExpr: "trunk.status.resource.portID == port.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..0f61f5c8e --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + portRef: trunk-create-full diff --git a/internal/controllers/trunk/tests/trunk-create-minimal/00-secret.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-create-minimal/01-assert.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/01-assert.yaml new file mode 100644 index 000000000..35eab2add --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk' in secret.metadata.finalizers" diff --git a/internal/controllers/trunk/tests/trunk-create-minimal/01-delete-secret.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-create-minimal/README.md b/internal/controllers/trunk/tests/trunk-create-minimal/README.md new file mode 100644 index 000000000..49a8df75e --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a Trunk with the minimum options + +## Step 00 + +Create a minimal Trunk, 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/trunk/tests/trunk-dependency/00-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml new file mode 100644 index 000000000..b5b6be241 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/trunk-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/trunk-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-port +status: + conditions: + - type: Available + message: Waiting for Port/trunk-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Port/trunk-dependency-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/trunk-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/trunk-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..11a5ba69f --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-port +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + portRef: trunk-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-project +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + portRef: trunk-dependency + projectRef: trunk-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: trunk-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + portRef: trunk-dependency diff --git a/internal/controllers/trunk/tests/trunk-dependency/00-secret.yaml b/internal/controllers/trunk/tests/trunk-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-dependency/01-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml new file mode 100644 index 000000000..c2a36dd02 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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: Trunk +metadata: + name: trunk-dependency-no-port +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-project +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml b/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..c78751b67 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic trunk-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-dependency-pending +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: trunk-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml new file mode 100644 index 000000000..7a14ca79c --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-dependency + ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: trunk-dependency + ref: project + - apiVersion: v1 + kind: Secret + name: trunk-dependency + ref: secret +assertAll: + - celExpr: "port.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/trunk' in port.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/trunk' in project.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/trunk' in secret.metadata.finalizers" diff --git a/internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml b/internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..f2cc3c3f4 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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 port.openstack.k-orc.cloud trunk-dependency --wait=false + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud trunk-dependency --wait=false + namespaced: true + - command: kubectl delete secret trunk-dependency --wait=false + namespaced: true diff --git a/internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml new file mode 100644 index 000000000..c408c33b8 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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 port.openstack.k-orc.cloud trunk-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud trunk-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret trunk-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml b/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..25f2a80c5 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-dependency-no-port +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-dependency-no-project diff --git a/internal/controllers/trunk/tests/trunk-dependency/README.md b/internal/controllers/trunk/tests/trunk-dependency/README.md new file mode 100644 index 000000000..14d0a8ec4 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create Trunks referencing non-existing resources. Each Trunk is dependent on other non-existing resource. Verify that the Trunks are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the Trunks 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 Trunks and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/00-assert.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/00-assert.yaml new file mode 100644 index 000000000..f6904cc7b --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/00-assert.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Port/trunk-import-dependency to be ready + Waiting for Project/trunk-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Port/trunk-import-dependency to be ready + Waiting for Project/trunk-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..2cf348d34 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: trunk-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: trunk-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: trunk-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + portRef: trunk-import-dependency + projectRef: trunk-import-dependency diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/00-secret.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-import-dependency/01-assert.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/01-assert.yaml new file mode 100644 index 000000000..8190ecc75 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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: Trunk +metadata: + name: trunk-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Port/trunk-import-dependency to be ready + Waiting for Project/trunk-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Port/trunk-import-dependency to be ready + Waiting for Project/trunk-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..b5aeb8971 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: trunk-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `trunk-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + portRef: trunk-import-dependency-not-this-one + portRef: trunk-import-dependency-not-this-one + projectRef: trunk-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/02-assert.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/02-assert.yaml new file mode 100644 index 000000000..a57266e42 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/02-assert.yaml @@ -0,0 +1,39 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-import-dependency + ref: trunk1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-import-dependency-not-this-one + ref: trunk2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-import-dependency + ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: trunk-import-dependency + ref: project +assertAll: + - celExpr: "trunk1.status.id != trunk2.status.id" + - celExpr: "trunk1.status.resource.portID == port.status.id" + - celExpr: "trunk1.status.resource.projectID == project.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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/trunk/tests/trunk-import-dependency/02-create-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..ca57bcc93 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml @@ -0,0 +1,55 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: trunk-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + portRef: trunk-import-dependency-external + portRef: trunk-import-dependency-external + projectRef: trunk-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/03-assert.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/03-assert.yaml new file mode 100644 index 000000000..a50d07a42 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/03-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get port.openstack.k-orc.cloud trunk-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud trunk-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..b818fa1a5 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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 port.openstack.k-orc.cloud trunk-import-dependency + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud trunk-import-dependency + namespaced: true diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/04-assert.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/04-assert.yaml new file mode 100644 index 000000000..059ed2ef6 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get trunk.openstack.k-orc.cloud trunk-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/04-delete-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..10b0d3c75 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-import-dependency diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/README.md b/internal/controllers/trunk/tests/trunk-import-dependency/README.md new file mode 100644 index 000000000..386f4830a --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported Trunk + +## Step 00 + +Import a Trunk that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the Trunk is waiting for the dependency to be ready. + +## Step 01 + +Create a Trunk matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a Trunk matching the import filters. + +Verify that the observed status on the imported Trunk corresponds to the spec of the created Trunk. + +## 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 Trunk and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/trunk/tests/trunk-import-error/00-assert.yaml b/internal/controllers/trunk/tests/trunk-import-error/00-assert.yaml new file mode 100644 index 000000000..6e538ab91 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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: Trunk +metadata: + name: trunk-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/trunk/tests/trunk-import-error/00-create-resources.yaml b/internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml new file mode 100644 index 000000000..fa08cde79 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-error +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-error-external-1 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Trunk from "import error" test + portRef: trunk-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-error-external-2 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Trunk from "import error" test + portRef: trunk-import-error + # TODO(scaffolding): add any required field diff --git a/internal/controllers/trunk/tests/trunk-import-error/00-secret.yaml b/internal/controllers/trunk/tests/trunk-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-import-error/01-assert.yaml b/internal/controllers/trunk/tests/trunk-import-error/01-assert.yaml new file mode 100644 index 000000000..1f48b6bb9 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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/trunk/tests/trunk-import-error/01-import-resource.yaml b/internal/controllers/trunk/tests/trunk-import-error/01-import-resource.yaml new file mode 100644 index 000000000..d3d922853 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: Trunk from "import error" test diff --git a/internal/controllers/trunk/tests/trunk-import-error/README.md b/internal/controllers/trunk/tests/trunk-import-error/README.md new file mode 100644 index 000000000..ce3ec498f --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import-error/README.md @@ -0,0 +1,13 @@ +# Import Trunk with more than one matching resources + +## Step 00 + +Create two Trunks with identical specs. + +## Step 01 + +Ensure that an imported Trunk with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/trunk/tests/trunk-import/00-assert.yaml b/internal/controllers/trunk/tests/trunk-import/00-assert.yaml new file mode 100644 index 000000000..4ee876cb5 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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/trunk/tests/trunk-import/00-import-resource.yaml b/internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml new file mode 100644 index 000000000..787ad1312 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: trunk-import-external + description: Trunk trunk-import-external from "trunk-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/trunk/tests/trunk-import/00-secret.yaml b/internal/controllers/trunk/tests/trunk-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-import/01-assert.yaml b/internal/controllers/trunk/tests/trunk-import/01-assert.yaml new file mode 100644 index 000000000..ed077e4ba --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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: trunk-import-external-not-this-one + description: Trunk trunk-import-external from "trunk-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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/trunk/tests/trunk-import/01-create-trap-resource.yaml b/internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..05ea99726 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `trunk-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: Trunk +metadata: + name: trunk-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Trunk trunk-import-external from "trunk-import" test + portRef: trunk-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/trunk/tests/trunk-import/02-assert.yaml b/internal/controllers/trunk/tests/trunk-import/02-assert.yaml new file mode 100644 index 000000000..607841b87 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-import-external + ref: trunk1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-import-external-not-this-one + ref: trunk2 +assertAll: + - celExpr: "trunk1.status.id != trunk2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-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: trunk-import-external + description: Trunk trunk-import-external from "trunk-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml b/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml new file mode 100644 index 000000000..2596a4bf5 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-import-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Trunk trunk-import-external from "trunk-import" test + portRef: trunk-import + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/trunk/tests/trunk-import/README.md b/internal/controllers/trunk/tests/trunk-import/README.md new file mode 100644 index 000000000..a0d99d2ce --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-import/README.md @@ -0,0 +1,18 @@ +# Import Trunk + +## Step 00 + +Import a trunk that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a trunk 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 trunk matching the filter and verify that the observed status on the imported trunk corresponds to the spec of the created trunk. +Also, confirm that it does not adopt any trunk whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/trunk/tests/trunk-update/00-assert.yaml b/internal/controllers/trunk/tests/trunk-update/00-assert.yaml new file mode 100644 index 000000000..f241702a3 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-update + ref: trunk +assertAll: + - celExpr: "!has(trunk.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +status: + resource: + name: trunk-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/trunk/tests/trunk-update/00-minimal-resource.yaml b/internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml new file mode 100644 index 000000000..1226197a8 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + portRef: trunk-update diff --git a/internal/controllers/trunk/tests/trunk-update/00-prerequisites.yaml b/internal/controllers/trunk/tests/trunk-update/00-prerequisites.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/00-prerequisites.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/trunk/tests/trunk-update/01-assert.yaml b/internal/controllers/trunk/tests/trunk-update/01-assert.yaml new file mode 100644 index 000000000..0750bc9ce --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +status: + resource: + name: trunk-update-updated + description: trunk-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/trunk/tests/trunk-update/01-updated-resource.yaml b/internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml new file mode 100644 index 000000000..4d9937eb0 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +spec: + resource: + name: trunk-update-updated + description: trunk-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/trunk/tests/trunk-update/02-assert.yaml b/internal/controllers/trunk/tests/trunk-update/02-assert.yaml new file mode 100644 index 000000000..33096164f --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-update + ref: trunk +assertAll: + - celExpr: "!has(trunk.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +status: + resource: + name: trunk-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/trunk/tests/trunk-update/02-reverted-resource.yaml b/internal/controllers/trunk/tests/trunk-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-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/trunk/tests/trunk-update/README.md b/internal/controllers/trunk/tests/trunk-update/README.md new file mode 100644 index 000000000..a7040db70 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/README.md @@ -0,0 +1,17 @@ +# Update Trunk + +## Step 00 + +Create a Trunk 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/trunk.go b/internal/osclients/trunk.go new file mode 100644 index 000000000..cfa3db90f --- /dev/null +++ b/internal/osclients/trunk.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/trunks" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type TrunkClient interface { + ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] + CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) + DeleteTrunk(ctx context.Context, resourceID string) error + GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) + UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) +} + +type trunkClient struct{ client *gophercloud.ServiceClient } + +// NewTrunkClient returns a new OpenStack client. +func NewTrunkClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (TrunkClient, 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 trunk service client: %v", err) + } + + return &trunkClient{client}, nil +} + +func (c trunkClient) ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] { + pager := trunks.List(c.client, listOpts) + return func(yield func(*trunks.Trunk, error) bool) { + _ = pager.EachPage(ctx, yieldPage(trunks.ExtractTrunks, yield)) + } +} + +func (c trunkClient) CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) { + return trunks.Create(ctx, c.client, opts).Extract() +} + +func (c trunkClient) DeleteTrunk(ctx context.Context, resourceID string) error { + return trunks.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c trunkClient) GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) { + return trunks.Get(ctx, c.client, resourceID).Extract() +} + +func (c trunkClient) UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) { + return trunks.Update(ctx, c.client, id, opts).Extract() +} + +type trunkErrorClient struct{ error } + +// NewTrunkErrorClient returns a TrunkClient in which every method returns the given error. +func NewTrunkErrorClient(e error) TrunkClient { + return trunkErrorClient{e} +} + +func (e trunkErrorClient) ListTrunks(_ context.Context, _ trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] { + return func(yield func(*trunks.Trunk, error) bool) { + yield(nil, e.error) + } +} + +func (e trunkErrorClient) CreateTrunk(_ context.Context, _ trunks.CreateOptsBuilder) (*trunks.Trunk, error) { + return nil, e.error +} + +func (e trunkErrorClient) DeleteTrunk(_ context.Context, _ string) error { + return e.error +} + +func (e trunkErrorClient) GetTrunk(_ context.Context, _ string) (*trunks.Trunk, error) { + return nil, e.error +} + +func (e trunkErrorClient) UpdateTrunk(_ context.Context, _ string, _ trunks.UpdateOptsBuilder) (*trunks.Trunk, error) { + return nil, e.error +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index f1664a185..d7e34fedc 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1632,6 +1632,8 @@ _Appears in:_ - [ServerVolumeSpec](#servervolumespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) +- [TrunkFilter](#trunkfilter) +- [TrunkResourceSpec](#trunkresourcespec) - [UserDataSpec](#userdataspec) - [VolumeResourceSpec](#volumeresourcespec) @@ -2025,6 +2027,8 @@ _Appears in:_ - [ServiceResourceSpec](#serviceresourcespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) +- [TrunkFilter](#trunkfilter) +- [TrunkResourceSpec](#trunkresourcespec) - [VolumeFilter](#volumefilter) - [VolumeResourceSpec](#volumeresourcespec) - [VolumeTypeFilter](#volumetypefilter) @@ -3823,6 +3827,12 @@ _Appears in:_ + + + + + + #### UserDataSpec From aa64e63d3377dde8f7c3e7131f594b670fbcc1de Mon Sep 17 00:00:00 2001 From: Mohammed Al-Dokimi Date: Fri, 9 Jan 2026 13:33:51 +0100 Subject: [PATCH 033/121] Trunk: implement API, controller, and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Martin André Trunk --- .github/workflows/e2e.yaml | 2 +- PROJECT | 8 + README.md | 1 + api/v1alpha1/trunk_types.go | 112 +++- api/v1alpha1/zz_generated.deepcopy.go | 218 +++++++- api/v1alpha1/zz_generated.trunk-resource.go | 177 ++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 521 ++++++++++++++++++ cmd/resource-generator/main.go | 4 + .../bases/openstack.k-orc.cloud_trunks.yaml | 502 +++++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + config/samples/openstack_v1alpha1_trunk.yaml | 14 +- .../kustomizeconfig/kustomizeconfig.yaml | 12 + go.mod | 2 +- internal/controllers/trunk/actuator.go | 212 +++++-- internal/controllers/trunk/actuator_test.go | 217 +++++++- internal/controllers/trunk/controller.go | 31 ++ internal/controllers/trunk/status.go | 33 +- .../tests/trunk-create-full/00-assert.yaml | 22 +- .../trunk-create-full/00-create-resource.yaml | 76 ++- .../tests/trunk-create-full/01-assert.yaml | 8 + .../01-set-adminstateup.yaml | 8 + .../trunk/tests/trunk-create-full/README.md | 4 + .../tests/trunk-create-minimal/00-assert.yaml | 12 +- .../00-create-resource.yaml | 38 +- .../tests/trunk-dependency/00-assert.yaml | 15 + .../00-create-resources-missing-deps.yaml | 91 ++- .../tests/trunk-dependency/01-assert.yaml | 15 + .../01-create-dependencies.yaml | 25 +- .../tests/trunk-dependency/02-assert.yaml | 8 +- .../02-delete-dependencies.yaml | 4 +- .../tests/trunk-dependency/03-assert.yaml | 4 +- .../trunk-dependency/03-delete-resources.yaml | 3 + .../00-import-resource.yaml | 4 +- .../01-create-trap-resource.yaml | 41 +- .../02-create-resource.yaml | 27 +- .../00-create-resources.yaml | 57 +- .../trunk-import/00-import-resource.yaml | 4 +- .../trunk/tests/trunk-import/01-assert.yaml | 4 +- .../trunk-import/01-create-trap-resource.yaml | 38 +- .../trunk/tests/trunk-import/02-assert.yaml | 4 +- .../trunk-import/02-create-resource.yaml | 12 +- .../trunk/tests/trunk-update/00-assert.yaml | 36 +- .../trunk-update/00-minimal-resource.yaml | 36 +- .../trunk/tests/trunk-update/01-assert.yaml | 35 +- .../trunk-update/01-updated-resource.yaml | 20 +- .../trunk/tests/trunk-update/02-assert.yaml | 13 +- .../tests/trunk-update/02-disable-trunk.yaml | 8 + .../trunk/tests/trunk-update/03-assert.yaml | 15 + .../tests/trunk-update/03-enable-trunk.yaml | 8 + .../trunk/tests/trunk-update/04-assert.yaml | 40 ++ ...esource.yaml => 04-reverted-resource.yaml} | 0 .../trunk/tests/trunk-update/README.md | 10 +- .../controllers/trunk/zz_generated.adapter.go | 88 +++ .../trunk/zz_generated.controller.go | 45 ++ internal/osclients/mock/networking.go | 103 ++++ internal/osclients/networking.go | 65 ++- internal/osclients/trunk.go | 104 ---- kuttl-test.yaml | 1 + .../applyconfiguration/api/v1alpha1/trunk.go | 281 ++++++++++ .../api/v1alpha1/trunkfilter.go | 120 ++++ .../api/v1alpha1/trunkimport.go | 48 ++ .../api/v1alpha1/trunkresourcespec.go | 104 ++++ .../api/v1alpha1/trunkresourcestatus.go | 147 +++++ .../api/v1alpha1/trunkspec.go | 79 +++ .../api/v1alpha1/trunkstatus.go | 66 +++ .../api/v1alpha1/trunksubportspec.go | 61 ++ .../api/v1alpha1/trunksubportstatus.go | 57 ++ .../applyconfiguration/internal/internal.go | 207 +++++++ pkg/clients/applyconfiguration/utils.go | 18 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../typed/api/v1alpha1/fake/fake_trunk.go | 49 ++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../clientset/typed/api/v1alpha1/trunk.go | 74 +++ .../api/v1alpha1/interface.go | 7 + .../externalversions/api/v1alpha1/trunk.go | 102 ++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + pkg/clients/listers/api/v1alpha1/trunk.go | 70 +++ website/docs/crd-reference.md | 194 +++++++ 82 files changed, 4598 insertions(+), 328 deletions(-) create mode 100644 api/v1alpha1/zz_generated.trunk-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_trunks.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-full/01-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-create-full/01-set-adminstateup.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/02-disable-trunk.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/03-assert.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/03-enable-trunk.yaml create mode 100644 internal/controllers/trunk/tests/trunk-update/04-assert.yaml rename internal/controllers/trunk/tests/trunk-update/{02-reverted-resource.yaml => 04-reverted-resource.yaml} (100%) create mode 100644 internal/controllers/trunk/zz_generated.adapter.go create mode 100644 internal/controllers/trunk/zz_generated.controller.go delete mode 100644 internal/osclients/trunk.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunk.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunkfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunkimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunksubportspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/trunksubportstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_trunk.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/trunk.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/trunk.go create mode 100644 pkg/clients/listers/api/v1alpha1/trunk.go diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index bee7d240f..c3a741a60 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -36,7 +36,7 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server" + enabled_services: "openstack-cli-server,neutron-trunk" - name: Deploy a Kind Cluster uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab diff --git a/PROJECT b/PROJECT index 8d6e2c12d..3d09e3c30 100644 --- a/PROJECT +++ b/PROJECT @@ -144,6 +144,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: Trunk + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/README.md b/README.md index 8ced52b8b..63d59ba73 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ kubectl delete -f $ORC_RELEASE | server group | | ✔ | ✔ | | service | | ✔ | ✔ | | subnet | | ◐ | ◐ | +| trunk | | ✔ | ✔ | | volume | | ◐ | ◐ | | volume type | | ◐ | ◐ | diff --git a/api/v1alpha1/trunk_types.go b/api/v1alpha1/trunk_types.go index 4f834f8c9..d857c4501 100644 --- a/api/v1alpha1/trunk_types.go +++ b/api/v1alpha1/trunk_types.go @@ -16,6 +16,45 @@ limitations under the License. package v1alpha1 +// TrunkSubportSpec represents a subport to attach to a trunk. +// It maps to gophercloud's trunks.Subport. +type TrunkSubportSpec struct { + // portRef is a reference to the ORC Port that will be attached as a subport. + // +required + PortRef KubernetesNameRef `json:"portRef,omitempty"` + + // segmentationID is the segmentation ID for the subport (e.g. VLAN ID). + // +required + // +kubebuilder:validation:Minimum:=1 + // +kubebuilder:validation:Maximum:=4094 + SegmentationID int32 `json:"segmentationID,omitempty"` + + // segmentationType is the segmentation type for the subport (e.g. vlan). + // +required + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=32 + // +kubebuilder:validation:Enum:=inherit;vlan + SegmentationType string `json:"segmentationType,omitempty"` +} + +// TrunkSubportStatus represents an attached subport on a trunk. +// It maps to gophercloud's trunks.Subport. +type TrunkSubportStatus struct { + // portID is the OpenStack ID of the Port attached as a subport. + // +kubebuilder:validation:MaxLength=1024 + // +optional + PortID string `json:"portID,omitempty"` + + // segmentationID is the segmentation ID for the subport (e.g. VLAN ID). + // +optional + SegmentationID int32 `json:"segmentationID,omitempty"` + + // segmentationType is the segmentation type for the subport (e.g. vlan). + // +kubebuilder:validation:MaxLength=1024 + // +optional + SegmentationType string `json:"segmentationType,omitempty"` +} + // TrunkResourceSpec contains the desired state of the resource. type TrunkResourceSpec struct { // name will be the name of the created resource. If not specified, the @@ -24,10 +63,8 @@ type TrunkResourceSpec struct { 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"` + Description *NeutronDescription `json:"description,omitempty"` // portRef is a reference to the ORC Port which this resource is associated with. // +required @@ -39,13 +76,22 @@ type TrunkResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the CreateOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks - // - // Until you have implemented mutability for the field, you must add a CEL validation - // preventing the field being modified: - // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` + // adminStateUp is the administrative state of the trunk. If false (down), + // the trunk does not forward packets. + // +optional + AdminStateUp *bool `json:"adminStateUp,omitempty"` + + // subports is the list of ports to attach to the trunk. + // +optional + // +kubebuilder:validation:MaxItems:=1024 + // +listType=atomic + Subports []TrunkSubportSpec `json:"subports,omitempty"` + + // tags is a list of Neutron tags to apply to the trunk. + // +kubebuilder:validation:MaxItems:=64 + // +listType=set + // +optional + Tags []NeutronTag `json:"tags,omitempty"` } // TrunkFilter defines an existing resource by its properties @@ -56,10 +102,8 @@ type TrunkFilter 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"` // portRef is a reference to the ORC Port which this resource is associated with. // +optional @@ -69,9 +113,14 @@ type TrunkFilter struct { // +optional ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ListOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks + // Contrary to what the neutron doc say, we can't filter by status + // https://github.com/gophercloud/gophercloud/issues/3626 + + // adminStateUp is the administrative state of the trunk. + // +optional + AdminStateUp *bool `json:"adminStateUp,omitempty"` + + FilterByNeutronTags `json:",inline"` } // TrunkResourceStatus represents the observed state of the resource. @@ -96,7 +145,32 @@ type TrunkResourceStatus struct { // +optional ProjectID string `json:"projectID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the Trunk structure from - // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks + // tenantID is the project owner of the trunk (alias of projectID in some deployments). + // +kubebuilder:validation:MaxLength=1024 + // +optional + TenantID string `json:"tenantID,omitempty"` + + // status indicates whether the trunk is currently operational. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Status string `json:"status,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"` + + // adminStateUp is the administrative state of the trunk. + // +optional + AdminStateUp *bool `json:"adminStateUp,omitempty"` + + // subports is a list of ports associated with the trunk. + // +kubebuilder:validation:MaxItems=1024 + // +listType=atomic + // +optional + Subports []TrunkSubportStatus `json:"subports,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 11968bc6e..1c1cb6d71 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5014,6 +5014,33 @@ func (in *SubnetStatus) DeepCopy() *SubnetStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Trunk) DeepCopyInto(out *Trunk) { + *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 Trunk. +func (in *Trunk) DeepCopy() *Trunk { + if in == nil { + return nil + } + out := new(Trunk) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Trunk) 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 *TrunkFilter) DeepCopyInto(out *TrunkFilter) { *out = *in @@ -5024,7 +5051,7 @@ func (in *TrunkFilter) DeepCopyInto(out *TrunkFilter) { } if in.Description != nil { in, out := &in.Description, &out.Description - *out = new(string) + *out = new(NeutronDescription) **out = **in } if in.PortRef != nil { @@ -5037,6 +5064,12 @@ func (in *TrunkFilter) DeepCopyInto(out *TrunkFilter) { *out = new(KubernetesNameRef) **out = **in } + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } + in.FilterByNeutronTags.DeepCopyInto(&out.FilterByNeutronTags) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkFilter. @@ -5049,6 +5082,63 @@ func (in *TrunkFilter) DeepCopy() *TrunkFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkImport) DeepCopyInto(out *TrunkImport) { + *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(TrunkFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkImport. +func (in *TrunkImport) DeepCopy() *TrunkImport { + if in == nil { + return nil + } + out := new(TrunkImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkList) DeepCopyInto(out *TrunkList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Trunk, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkList. +func (in *TrunkList) DeepCopy() *TrunkList { + if in == nil { + return nil + } + out := new(TrunkList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrunkList) 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 *TrunkResourceSpec) DeepCopyInto(out *TrunkResourceSpec) { *out = *in @@ -5059,7 +5149,7 @@ func (in *TrunkResourceSpec) DeepCopyInto(out *TrunkResourceSpec) { } if in.Description != nil { in, out := &in.Description, &out.Description - *out = new(string) + *out = new(NeutronDescription) **out = **in } if in.ProjectRef != nil { @@ -5067,6 +5157,21 @@ func (in *TrunkResourceSpec) DeepCopyInto(out *TrunkResourceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } + if in.Subports != nil { + in, out := &in.Subports, &out.Subports + *out = make([]TrunkSubportSpec, len(*in)) + copy(*out, *in) + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]NeutronTag, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkResourceSpec. @@ -5082,6 +5187,22 @@ func (in *TrunkResourceSpec) DeepCopy() *TrunkResourceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TrunkResourceStatus) DeepCopyInto(out *TrunkResourceStatus) { *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) + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } + if in.Subports != nil { + in, out := &in.Subports, &out.Subports + *out = make([]TrunkSubportStatus, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkResourceStatus. @@ -5094,6 +5215,99 @@ func (in *TrunkResourceStatus) DeepCopy() *TrunkResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkSpec) DeepCopyInto(out *TrunkSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(TrunkImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(TrunkResourceSpec) + (*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 TrunkSpec. +func (in *TrunkSpec) DeepCopy() *TrunkSpec { + if in == nil { + return nil + } + out := new(TrunkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkStatus) DeepCopyInto(out *TrunkStatus) { + *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(TrunkResourceStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkStatus. +func (in *TrunkStatus) DeepCopy() *TrunkStatus { + if in == nil { + return nil + } + out := new(TrunkStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkSubportSpec) DeepCopyInto(out *TrunkSubportSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkSubportSpec. +func (in *TrunkSubportSpec) DeepCopy() *TrunkSubportSpec { + if in == nil { + return nil + } + out := new(TrunkSubportSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrunkSubportStatus) DeepCopyInto(out *TrunkSubportStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkSubportStatus. +func (in *TrunkSubportStatus) DeepCopy() *TrunkSubportStatus { + if in == nil { + return nil + } + out := new(TrunkSubportStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UserDataSpec) DeepCopyInto(out *UserDataSpec) { *out = *in diff --git a/api/v1alpha1/zz_generated.trunk-resource.go b/api/v1alpha1/zz_generated.trunk-resource.go new file mode 100644 index 000000000..305ac1878 --- /dev/null +++ b/api/v1alpha1/zz_generated.trunk-resource.go @@ -0,0 +1,177 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TrunkImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type TrunkImport struct { + // id contains the unique identifier of an existing OpenStack resource. Note + // that when specifying an import by ID, the resource MUST already exist. + // The ORC object will enter an error state if the resource does not exist. + // +optional + // +kubebuilder:validation:Format:=uuid + ID *string `json:"id,omitempty"` + + // filter contains a resource query which is expected to return a single + // result. The controller will continue to retry if filter returns no + // results. If filter returns multiple results the controller will set an + // error state and will not continue to retry. + // +optional + Filter *TrunkFilter `json:"filter,omitempty"` +} + +// TrunkSpec 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 TrunkSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *TrunkImport `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 *TrunkResourceSpec `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"` +} + +// TrunkStatus defines the observed state of an ORC resource. +type TrunkStatus struct { + // conditions represents the observed status of the object. + // Known .status.conditions.type are: "Available", "Progressing" + // + // Available represents the availability of the OpenStack resource. If it is + // true then the resource is ready for use. + // + // Progressing indicates whether the controller is still attempting to + // reconcile the current state of the OpenStack resource to the desired + // state. Progressing will be False either because the desired state has + // been achieved, or because some terminal error prevents it from ever being + // achieved and the controller is no longer attempting to reconcile. If + // Progressing is True, an observer waiting on the resource should continue + // to wait. + // + // +kubebuilder:validation:MaxItems:=32 + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // id is the unique identifier of the OpenStack resource. + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *TrunkResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &Trunk{} + +func (i *Trunk) 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" + +// Trunk is the Schema for an ORC resource. +type Trunk struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +optional + Spec TrunkSpec `json:"spec,omitempty"` + + // status defines the observed state of the resource. + // +optional + Status TrunkStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// TrunkList contains a list of Trunk. +type TrunkList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of Trunk. + // +required + Items []Trunk `json:"items"` +} + +func (l *TrunkList) GetItems() []Trunk { + return l.Items +} + +func init() { + SchemeBuilder.Register(&Trunk{}, &TrunkList{}) +} + +func (i *Trunk) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &Trunk{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 293aa2bab..bf9bc38ca 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -45,6 +45,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/servergroup" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/service" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/subnet" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/trunk" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volume" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volumetype" internalmanager "github.com/k-orc/openstack-resource-controller/v2/internal/manager" @@ -113,6 +114,7 @@ func main() { router.New(scopeFactory), routerinterface.New(scopeFactory), port.New(scopeFactory), + trunk.New(scopeFactory), floatingip.New(scopeFactory), flavor.New(scopeFactory), securitygroup.New(scopeFactory), diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 44fa7307a..bd2e18aa7 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -201,9 +201,16 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "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), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Trunk": schema_openstack_resource_controller_v2_api_v1alpha1_Trunk(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkImport": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkList": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkList(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportSpec": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Volume": schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeAttachmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeAttachmentStatus(ref), @@ -9744,6 +9751,56 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_Trunk(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Trunk 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.TrunkSpec"), + }, + }, + "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.TrunkStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -9779,9 +9836,175 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkFilter(ref common Format: "", }, }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the trunk.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tags": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "tags is a list of tags to filter by. If specified, the resource must have all of the tags specified to be included in the result.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "tagsAny": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "tagsAny is a list of tags to filter by. If specified, the resource must have at least one of the tags specified to be included in the result.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "notTags": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "notTags is a list of tags to filter by. If specified, resources which contain all of the given tags will be excluded from the result.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "notTagsAny": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "notTagsAny is a list of tags to filter by. If specified, resources which contain any of the given tags will be excluded from the result.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkImport 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.TrunkFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkList contains a list of Trunk.", + 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 Trunk.", + 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.Trunk"), + }, + }, + }, + }, + }, }, + Required: []string{"items"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Trunk", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } @@ -9820,10 +10043,58 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceSpec(ref Format: "", }, }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the trunk. If false (down), the trunk does not forward packets.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "subports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "subports is the list of ports to attach to the trunk.", + 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.TrunkSubportSpec"), + }, + }, + }, + }, + }, + "tags": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "tags is a list of Neutron tags to apply to the trunk.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, Required: []string{"portRef"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportSpec"}, } } @@ -9862,6 +10133,256 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkResourceStatus(re Format: "", }, }, + "tenantID": { + SchemaProps: spec.SchemaProps{ + Description: "tenantID is the project owner of the trunk (alias of projectID in some deployments).", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status indicates whether the trunk is currently operational.", + Type: []string{"string"}, + Format: "", + }, + }, + "tags": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "tags is the list of tags on the resource.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "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", + }, + }, + "adminStateUp": { + SchemaProps: spec.SchemaProps{ + Description: "adminStateUp is the administrative state of the trunk.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "subports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "subports is a list of ports associated with the trunk.", + 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.TrunkSubportStatus"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkSpec 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.TrunkImport"), + }, + }, + "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.TrunkResourceSpec"), + }, + }, + "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.TrunkImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkStatus 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.TrunkResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkSubportSpec represents a subport to attach to a trunk. It maps to gophercloud's trunks.Subport.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "portRef": { + SchemaProps: spec.SchemaProps{ + Description: "portRef is a reference to the ORC Port that will be attached as a subport.", + Type: []string{"string"}, + Format: "", + }, + }, + "segmentationID": { + SchemaProps: spec.SchemaProps{ + Description: "segmentationID is the segmentation ID for the subport (e.g. VLAN ID).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "segmentationType": { + SchemaProps: spec.SchemaProps{ + Description: "segmentationType is the segmentation type for the subport (e.g. vlan).", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"portRef", "segmentationID", "segmentationType"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TrunkSubportStatus represents an attached subport on a trunk. It maps to gophercloud's trunks.Subport.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "portID": { + SchemaProps: spec.SchemaProps{ + Description: "portID is the OpenStack ID of the Port attached as a subport.", + Type: []string{"string"}, + Format: "", + }, + }, + "segmentationID": { + SchemaProps: spec.SchemaProps{ + Description: "segmentationID is the segmentation ID for the subport (e.g. VLAN ID).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "segmentationType": { + SchemaProps: spec.SchemaProps{ + Description: "segmentationType is the segmentation type for the subport (e.g. vlan).", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index b3f4ece76..45e9d364b 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -144,6 +144,10 @@ var resources []templateFields = []templateFields{ Name: "Subnet", ExistingOSClient: true, }, + { + Name: "Trunk", + ExistingOSClient: true, + }, { Name: "Volume", }, diff --git a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml new file mode 100644 index 000000000..a8c855f63 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml @@ -0,0 +1,502 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: trunks.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: Trunk + listKind: TrunkList + plural: trunks + singular: trunk + 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: Trunk 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: + adminStateUp: + description: adminStateUp is the administrative state of the + trunk. + type: boolean + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + name: + description: name of the existing resource + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + notTags: + description: |- + notTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + 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 + portRef: + description: portRef is a reference to the ORC Port which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + projectRef: + description: projectRef is a reference to the ORC Project + which this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + tags: + description: |- + tags is a list of tags to filter by. If specified, the resource must + have all of the tags specified to be included in the result. + items: + 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: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + adminStateUp: + description: |- + adminStateUp is the administrative state of the trunk. If false (down), + the trunk does not forward packets. + type: boolean + 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 + portRef: + description: portRef is a reference to the ORC Port which this + resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: portRef is immutable + rule: self == oldSelf + 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 + subports: + description: subports is the list of ports to attach to the trunk. + items: + description: |- + TrunkSubportSpec represents a subport to attach to a trunk. + It maps to gophercloud's trunks.Subport. + properties: + portRef: + description: portRef is a reference to the ORC Port that + will be attached as a subport. + maxLength: 253 + minLength: 1 + type: string + segmentationID: + description: segmentationID is the segmentation ID for the + subport (e.g. VLAN ID). + format: int32 + maximum: 4094 + minimum: 1 + type: integer + segmentationType: + description: segmentationType is the segmentation type for + the subport (e.g. vlan). + enum: + - inherit + - vlan + maxLength: 32 + minLength: 1 + type: string + required: + - portRef + - segmentationID + - segmentationType + type: object + maxItems: 1024 + type: array + x-kubernetes-list-type: atomic + tags: + description: tags is a list of Neutron tags to apply to the trunk. + 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 + required: + - portRef + type: object + required: + - cloudCredentialsRef + type: object + x-kubernetes-validations: + - message: resource must be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true' + - message: import may not be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__) + : true' + - message: resource may not be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource) + : true' + - message: import must be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__) + : true' + - message: managedOptions may only be provided when policy is managed + rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed'' + : true' + status: + description: status defines the observed state of the resource. + properties: + conditions: + description: |- + conditions represents the observed status of the object. + Known .status.conditions.type are: "Available", "Progressing" + + Available represents the availability of the OpenStack resource. If it is + true then the resource is ready for use. + + Progressing indicates whether the controller is still attempting to + reconcile the current state of the OpenStack resource to the desired + state. Progressing will be False either because the desired state has + been achieved, or because some terminal error prevents it from ever being + achieved and the controller is no longer attempting to reconcile. If + Progressing is True, an observer waiting on the resource should continue + to wait. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + id: + description: id is the unique identifier of the OpenStack resource. + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + adminStateUp: + description: adminStateUp is the administrative state of the trunk. + type: boolean + 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 + 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 + portID: + description: portID is the ID of the Port to which the resource + is associated. + maxLength: 1024 + type: string + 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 + status: + description: status indicates whether the trunk is currently operational. + maxLength: 1024 + type: string + subports: + description: subports is a list of ports associated with the trunk. + items: + description: |- + TrunkSubportStatus represents an attached subport on a trunk. + It maps to gophercloud's trunks.Subport. + properties: + portID: + description: portID is the OpenStack ID of the Port attached + as a subport. + maxLength: 1024 + type: string + segmentationID: + description: segmentationID is the segmentation ID for the + subport (e.g. VLAN ID). + format: int32 + type: integer + segmentationType: + description: segmentationType is the segmentation type for + the subport (e.g. vlan). + maxLength: 1024 + type: string + type: object + maxItems: 1024 + type: array + x-kubernetes-list-type: atomic + 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 + tenantID: + description: tenantID is the project owner of the trunk (alias + of projectID in some deployments). + maxLength: 1024 + type: string + 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 + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 33b8c85e2..b73dcac05 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -20,6 +20,7 @@ resources: - bases/openstack.k-orc.cloud_servergroups.yaml - bases/openstack.k-orc.cloud_services.yaml - bases/openstack.k-orc.cloud_subnets.yaml +- bases/openstack.k-orc.cloud_trunks.yaml - bases/openstack.k-orc.cloud_volumes.yaml - bases/openstack.k-orc.cloud_volumetypes.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index dac467c69..aa2a75e43 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -18,6 +18,7 @@ resources: - openstack_v1alpha1_servergroup.yaml - openstack_v1alpha1_service.yaml - openstack_v1alpha1_subnet.yaml +- openstack_v1alpha1_trunk.yaml - openstack_v1alpha1_volume.yaml - openstack_v1alpha1_volumetype.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/openstack_v1alpha1_trunk.yaml b/config/samples/openstack_v1alpha1_trunk.yaml index cb038421b..7019315f1 100644 --- a/config/samples/openstack_v1alpha1_trunk.yaml +++ b/config/samples/openstack_v1alpha1_trunk.yaml @@ -5,10 +5,20 @@ metadata: name: trunk-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 Trunk - # TODO(scaffolding): Add all fields the resource supports + name: trunk-sample-name + portRef: my-port + subPorts: + - portRef: sub-port-1 + segmentationID: 101 + segmentationType: vlan + - portRef: sub-port-2 + segmentationID: 102 + segmentationType: vlan + tags: + - tag1 + - tag2 diff --git a/examples/components/kustomizeconfig/kustomizeconfig.yaml b/examples/components/kustomizeconfig/kustomizeconfig.yaml index 90866d4e5..c8439d33b 100644 --- a/examples/components/kustomizeconfig/kustomizeconfig.yaml +++ b/examples/components/kustomizeconfig/kustomizeconfig.yaml @@ -25,6 +25,8 @@ nameReference: kind: Subnet - path: spec/cloudCredentialsRef/secretName kind: KeyPair + - path: spec/cloudCredentialsRef/secretName + kind: Trunk - kind: Network fieldSpecs: @@ -77,6 +79,12 @@ nameReference: kind: FloatingIP - path: spec/resource/ports[]/portRef kind: Server + - path: spec/resource/portRef + kind: Trunk + - path: spec/resource/subports[]/portRef + kind: Trunk + - path: spec/import/filter/portRef + kind: Trunk - kind: Project fieldSpecs: @@ -90,3 +98,7 @@ nameReference: kind: Port - path: spec/resource/projectRef kind: SecurityGroup + - path: spec/resource/projectRef + kind: Trunk + - path: spec/import/filter/projectRef + kind: Trunk diff --git a/go.mod b/go.mod index fdcdbb00d..3f9d65d29 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-logr/logr v1.4.3 + github.com/google/go-cmp v0.7.0 github.com/gophercloud/gophercloud/v2 v2.10.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 github.com/onsi/ginkgo/v2 v2.28.1 @@ -49,7 +50,6 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect diff --git a/internal/controllers/trunk/actuator.go b/internal/controllers/trunk/actuator.go index facc802e1..86cd107fb 100644 --- a/internal/controllers/trunk/actuator.go +++ b/internal/controllers/trunk/actuator.go @@ -18,6 +18,7 @@ package trunk import ( "context" + "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks" @@ -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 @@ -46,7 +48,7 @@ type ( ) type trunkActuator struct { - osClient osclients.TrunkClient + osClient osclients.NetworkClient k8sClient client.Client } @@ -71,22 +73,15 @@ func (actuator trunkActuator) ListOSResourcesForAdoption(ctx context.Context, or 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 := trunks.ListOpts{ Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Description: string(ptr.Deref(resourceSpec.Description, "")), } return actuator.osClient.ListTrunks(ctx, listOpts), true } func (actuator trunkActuator) 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 port, rs := dependency.FetchDependency( @@ -108,11 +103,15 @@ func (actuator trunkActuator) ListOSResourcesForImport(ctx context.Context, obj } listOpts := trunks.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - PortID: ptr.Deref(port.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + PortID: ptr.Deref(port.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + AdminStateUp: filter.AdminStateUp, + Tags: tags.Join(filter.Tags), + TagsAny: tags.Join(filter.TagsAny), + NotTags: tags.Join(filter.NotTags), + NotTagsAny: tags.Join(filter.NotTagsAny), } return actuator.osClient.ListTrunks(ctx, listOpts), reconcileStatus @@ -129,15 +128,15 @@ func (actuator trunkActuator) CreateResource(ctx context.Context, obj orcObjectP var reconcileStatus progress.ReconcileStatus var portID string - port, portDepRS := portDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS) - if port != nil { - portID = ptr.Deref(port.Status.ID, "") - } + port, portDepRS := portDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS) + if port != nil { + portID = ptr.Deref(port.Status.ID, "") + } var projectID string if resource.ProjectRef != nil { @@ -151,15 +150,43 @@ func (actuator trunkActuator) CreateResource(ctx context.Context, obj orcObjectP projectID = ptr.Deref(project.Status.ID, "") } } + + // Resolve subport port dependencies + var subports []trunks.Subport + if len(resource.Subports) > 0 { + subportPortMap, subportPortDepRS := subportPortDependency.GetDependencies( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(subportPortDepRS) + if needsReschedule, _ := subportPortDepRS.NeedsReschedule(); !needsReschedule { + subports = make([]trunks.Subport, len(resource.Subports)) + for i := range resource.Subports { + subportSpec := &resource.Subports[i] + port, ok := subportPortMap[string(subportSpec.PortRef)] + if !ok { + return nil, reconcileStatus.WithError(fmt.Errorf("unable to resolve required subport port reference: %s", subportSpec.PortRef)) + } + subports[i] = trunks.Subport{ + PortID: ptr.Deref(port.Status.ID, ""), + SegmentationID: int(subportSpec.SegmentationID), + SegmentationType: subportSpec.SegmentationType, + } + } + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } createOpts := trunks.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - PortID: portID, - ProjectID: projectID, - // TODO(scaffolding): Add more fields + Name: getResourceName(obj), + Description: string(ptr.Deref(resource.Description, "")), + PortID: portID, + ProjectID: projectID, + AdminStateUp: resource.AdminStateUp, + Subports: subports, } osResource, err := actuator.osClient.CreateTrunk(ctx, createOpts) @@ -191,8 +218,7 @@ func (actuator trunkActuator) updateResource(ctx context.Context, obj orcObjectP handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleAdminStateUpUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -240,14 +266,136 @@ func handleNameUpdate(updateOpts *trunks.UpdateOpts, obj orcObjectPT, osResource } func handleDescriptionUpdate(updateOpts *trunks.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - description := ptr.Deref(resource.Description, "") + description := string(ptr.Deref(resource.Description, "")) if osResource.Description != description { updateOpts.Description = &description } } +func handleAdminStateUpUpdate(updateOpts *trunks.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + // Default is true + adminStateUp := ptr.Deref(resource.AdminStateUp, true) + if osResource.AdminStateUp != adminStateUp { + updateOpts.AdminStateUp = &adminStateUp + } +} + +func (actuator trunkActuator) reconcileSubports(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + return nil + } + + var reconcileStatus progress.ReconcileStatus + + // Build desired subports map: portID -> subport spec + desiredSubports := make(map[string]*orcv1alpha1.TrunkSubportSpec, len(osResource.Subports)) + if len(resource.Subports) > 0 { + subportPortMap, subportPortDepRS := subportPortDependency.GetDependencies( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(subportPortDepRS) + if needsReschedule, _ := subportPortDepRS.NeedsReschedule(); needsReschedule { + return reconcileStatus + } + + for i := range resource.Subports { + subportSpec := &resource.Subports[i] + port, ok := subportPortMap[string(subportSpec.PortRef)] + if !ok { + return reconcileStatus.WithError(fmt.Errorf("unable to resolve required subport port reference: %s", subportSpec.PortRef)) + } + portID := ptr.Deref(port.Status.ID, "") + if portID == "" { + return reconcileStatus.WithError(fmt.Errorf("subport port %s does not have an ID", subportSpec.PortRef)) + } + desiredSubports[portID] = subportSpec + } + } + + // Build actual subports map: portID -> subport + actualSubports := make(map[string]trunks.Subport) + for i := range osResource.Subports { + sp := osResource.Subports[i] + actualSubports[sp.PortID] = sp + } + + // Determine subports to add and remove + var subportsToAdd []trunks.Subport + var subportsToRemove []trunks.RemoveSubport + + // Find subports to add (in desired but not in actual, or different segmentation) + for portID, desiredSpec := range desiredSubports { + actual, exists := actualSubports[portID] + if !exists { + // Need to add this subport + subportsToAdd = append(subportsToAdd, trunks.Subport{ + PortID: portID, + SegmentationID: int(desiredSpec.SegmentationID), + SegmentationType: desiredSpec.SegmentationType, + }) + } else if actual.SegmentationID != int(desiredSpec.SegmentationID) || actual.SegmentationType != desiredSpec.SegmentationType { + // Segmentation changed - need to remove and re-add + subportsToRemove = append(subportsToRemove, trunks.RemoveSubport{PortID: portID}) + subportsToAdd = append(subportsToAdd, trunks.Subport{ + PortID: portID, + SegmentationID: int(desiredSpec.SegmentationID), + SegmentationType: desiredSpec.SegmentationType, + }) + } + } + + // Find subports to remove (in actual but not in desired) + for portID := range actualSubports { + if _, exists := desiredSubports[portID]; !exists { + subportsToRemove = append(subportsToRemove, trunks.RemoveSubport{PortID: portID}) + } + } + + // Apply changes - remove first, then add + // This ensures that if we're updating a subport (remove + add), the remove happens first + if len(subportsToRemove) > 0 { + log.V(logging.Debug).Info("Removing subports", "count", len(subportsToRemove)) + removeOpts := trunks.RemoveSubportsOpts{ + Subports: subportsToRemove, + } + if err := actuator.osClient.RemoveSubports(ctx, osResource.ID, removeOpts); err != nil { + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration removing subports: "+err.Error(), err) + } + return reconcileStatus.WithError(err) + } + // Always refresh after removing subports, especially if we're also adding some + reconcileStatus = reconcileStatus.WithReconcileStatus(progress.NeedsRefresh()) + } + if len(subportsToAdd) > 0 { + log.V(logging.Debug).Info("Adding subports", "count", len(subportsToAdd)) + addOpts := trunks.AddSubportsOpts{ + Subports: subportsToAdd, + } + if _, err := actuator.osClient.AddSubports(ctx, osResource.ID, addOpts); err != nil { + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration adding subports: "+err.Error(), err) + } + return reconcileStatus.WithError(err) + } + reconcileStatus = reconcileStatus.WithReconcileStatus(progress.NeedsRefresh()) + } + + if len(subportsToAdd) == 0 && len(subportsToRemove) == 0 { + log.V(logging.Debug).Info("No subport changes") + } + + return reconcileStatus +} + func (actuator trunkActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ + tags.ReconcileTags[orcObjectPT, osResourceT](orcObject.Spec.Resource.Tags, osResource.Tags, tags.NewNeutronTagReplacer(actuator.osClient, "trunks", osResource.ID)), + actuator.reconcileSubports, actuator.updateResource, }, nil } @@ -269,7 +417,7 @@ func newActuator(ctx context.Context, orcObject *orcv1alpha1.Trunk, controller i if err != nil { return trunkActuator{}, progress.WrapError(err) } - osClient, err := clientScope.NewTrunkClient() + osClient, err := clientScope.NewNetworkClient() if err != nil { return trunkActuator{}, progress.WrapError(err) } diff --git a/internal/controllers/trunk/actuator_test.go b/internal/controllers/trunk/actuator_test.go index 66e8d3a1a..aa2981fa6 100644 --- a/internal/controllers/trunk/actuator_test.go +++ b/internal/controllers/trunk/actuator_test.go @@ -17,8 +17,10 @@ limitations under the License. package trunk import ( + "slices" "testing" + "github.com/google/go-cmp/cmp" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "k8s.io/utils/ptr" @@ -40,6 +42,16 @@ func TestNeedsUpdate(t *testing.T) { updateOpts: trunks.UpdateOpts{Name: ptr.To("updated")}, expectChange: true, }, + { + name: "RevisionNumber only should not require update", + updateOpts: trunks.UpdateOpts{RevisionNumber: ptr.To(10)}, + expectChange: false, + }, + { + name: "Name + RevisionNumber should require update", + updateOpts: trunks.UpdateOpts{Name: ptr.To("updated"), RevisionNumber: ptr.To(10)}, + expectChange: true, + }, } for _, tt := range testCases { @@ -88,10 +100,10 @@ func TestHandleNameUpdate(t *testing.T) { } func TestHandleDescriptionUpdate(t *testing.T) { - ptrToDescription := ptr.To[string] + ptrToDescription := ptr.To[orcv1alpha1.NeutronDescription] testCases := []struct { name string - newValue *string + newValue *orcv1alpha1.NeutronDescription existingValue string expectChange bool }{ @@ -117,3 +129,204 @@ func TestHandleDescriptionUpdate(t *testing.T) { } } + +func TestHandleAdminStateUpUpdate(t *testing.T) { + ptrToBool := ptr.To[bool] + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Identical true", newValue: ptrToBool(true), existingValue: true, expectChange: false}, + {name: "Identical false", newValue: ptrToBool(false), existingValue: false, expectChange: false}, + {name: "Different (true -> false)", newValue: ptrToBool(false), existingValue: true, expectChange: true}, + {name: "Different (false -> true)", newValue: ptrToBool(true), existingValue: false, expectChange: true}, + {name: "Nil means default true (existing true)", newValue: nil, existingValue: true, expectChange: false}, + {name: "Nil means default true (existing false)", newValue: nil, existingValue: false, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.TrunkResourceSpec{AdminStateUp: tt.newValue} + osResource := &osResourceT{AdminStateUp: tt.existingValue} + + updateOpts := trunks.UpdateOpts{} + handleAdminStateUpUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestReconcileSubportsLogic(t *testing.T) { + testCases := []struct { + name string + desiredSubports map[string]*orcv1alpha1.TrunkSubportSpec + actualSubports map[string]trunks.Subport + expectedSubportsToAdd []trunks.Subport + expectedSubportsRemove []trunks.Subport + }{ + { + name: "No changes needed", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{ + "port1": {SegmentationID: 100, SegmentationType: "vlan"}, + }, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + }, + expectedSubportsToAdd: []trunks.Subport{}, + expectedSubportsRemove: []trunks.Subport{}, + }, + { + name: "Add new subport", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{ + "port1": {SegmentationID: 100, SegmentationType: "vlan"}, + "port2": {SegmentationID: 200, SegmentationType: "vlan"}, + }, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + }, + expectedSubportsToAdd: []trunks.Subport{ + {PortID: "port2", SegmentationID: 200, SegmentationType: "vlan"}, + }, + expectedSubportsRemove: []trunks.Subport{}, + }, + { + name: "Remove subport", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{ + "port1": {SegmentationID: 100, SegmentationType: "vlan"}, + }, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + "port2": {PortID: "port2", SegmentationID: 200, SegmentationType: "vlan"}, + }, + expectedSubportsToAdd: []trunks.Subport{}, + expectedSubportsRemove: []trunks.Subport{ + {PortID: "port2"}, + }, + }, + { + name: "Update segmentation", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{ + "port1": {SegmentationID: 150, SegmentationType: "vlan"}, + }, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + }, + expectedSubportsToAdd: []trunks.Subport{ + {PortID: "port1", SegmentationID: 150, SegmentationType: "vlan"}, + }, + expectedSubportsRemove: []trunks.Subport{ + {PortID: "port1"}, + }, + }, + { + name: "Update segmentation type", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{ + "port1": {SegmentationID: 100, SegmentationType: "inherit"}, + }, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + }, + expectedSubportsToAdd: []trunks.Subport{ + {PortID: "port1", SegmentationID: 100, SegmentationType: "inherit"}, + }, + expectedSubportsRemove: []trunks.Subport{ + {PortID: "port1"}, + }, + }, + { + name: "Remove all subports", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{}, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + "port2": {PortID: "port2", SegmentationID: 200, SegmentationType: "vlan"}, + }, + expectedSubportsToAdd: []trunks.Subport{}, + expectedSubportsRemove: []trunks.Subport{ + {PortID: "port1"}, + {PortID: "port2"}, + }, + }, + { + name: "Complex update: add, remove, and modify", + desiredSubports: map[string]*orcv1alpha1.TrunkSubportSpec{ + "port1": {SegmentationID: 150, SegmentationType: "vlan"}, // modified + "port3": {SegmentationID: 300, SegmentationType: "vlan"}, // new + }, + actualSubports: map[string]trunks.Subport{ + "port1": {PortID: "port1", SegmentationID: 100, SegmentationType: "vlan"}, + "port2": {PortID: "port2", SegmentationID: 200, SegmentationType: "vlan"}, // removed + }, + expectedSubportsToAdd: []trunks.Subport{ + {PortID: "port1", SegmentationID: 150, SegmentationType: "vlan"}, // modified + {PortID: "port3", SegmentationID: 300, SegmentationType: "vlan"}, // new + }, + expectedSubportsRemove: []trunks.Subport{ + {PortID: "port1"}, // for modification + {PortID: "port2"}, // removed + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + subportsToAdd := []trunks.Subport{} + subportsToRemove := []trunks.Subport{} + + // Find subports to add (in desired but not in actual, or different segmentation) + for portID, desiredSpec := range tt.desiredSubports { + actual, exists := tt.actualSubports[portID] + if !exists { + // Need to add this subport + subportsToAdd = append(subportsToAdd, trunks.Subport{ + PortID: portID, + SegmentationID: int(desiredSpec.SegmentationID), + SegmentationType: desiredSpec.SegmentationType, + }) + } else if actual.SegmentationID != int(desiredSpec.SegmentationID) || actual.SegmentationType != desiredSpec.SegmentationType { + // Segmentation changed - need to remove and re-add + subportsToRemove = append(subportsToRemove, trunks.Subport{PortID: portID}) + subportsToAdd = append(subportsToAdd, trunks.Subport{ + PortID: portID, + SegmentationID: int(desiredSpec.SegmentationID), + SegmentationType: desiredSpec.SegmentationType, + }) + } + } + + // Find subports to remove (in actual but not in desired) + for portID := range tt.actualSubports { + if _, exists := tt.desiredSubports[portID]; !exists { + subportsToRemove = append(subportsToRemove, trunks.Subport{PortID: portID}) + } + } + + // Sort slices by PortID for deterministic comparison + sortByPortID := func(a, b trunks.Subport) int { + if a.PortID < b.PortID { + return -1 + } + if a.PortID > b.PortID { + return 1 + } + return 0 + } + slices.SortFunc(subportsToAdd, sortByPortID) + slices.SortFunc(subportsToRemove, sortByPortID) + slices.SortFunc(tt.expectedSubportsToAdd, sortByPortID) + slices.SortFunc(tt.expectedSubportsRemove, sortByPortID) + + if diff := cmp.Diff(tt.expectedSubportsToAdd, subportsToAdd); diff != "" { + t.Errorf("Subports to add mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(tt.expectedSubportsRemove, subportsToRemove); diff != "" { + t.Errorf("Subports to remove mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/controllers/trunk/controller.go b/internal/controllers/trunk/controller.go index 8cd733ff5..ce8b13f2e 100644 --- a/internal/controllers/trunk/controller.go +++ b/internal/controllers/trunk/controller.go @@ -31,6 +31,7 @@ import ( "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" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" "github.com/k-orc/openstack-resource-controller/v2/pkg/predicates" ) @@ -97,6 +98,26 @@ var projectImportDependency = dependency.NewDependency[*orcv1alpha1.TrunkList, * }, ) +var subportPortDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.TrunkList, *orcv1alpha1.Port]( + "spec.resource.subports[].portRef", + func(trunk *orcv1alpha1.Trunk) []string { + resource := trunk.Spec.Resource + if resource == nil { + return nil + } + if len(resource.Subports) == 0 { + return nil + } + portRefs := make([]string, 0, len(resource.Subports)) + for i := range resource.Subports { + portRefs = append(portRefs, string(resource.Subports[i].PortRef)) + } + return portRefs + }, + orcstrings.GetFinalizerName("trunk-subport"), externalObjectFieldOwner, + dependency.OverrideDependencyName("subport_port"), +) + // SetupWithManager sets up the controller with the Manager. func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) @@ -122,6 +143,11 @@ func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ct return err } + subportPortWatchEventHandler, err := subportPortDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). Watches(&orcv1alpha1.Port{}, portWatchEventHandler, @@ -138,6 +164,10 @@ func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ct Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), ). + // Watch for subport port changes + Watches(&orcv1alpha1.Port{}, subportPortWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Port{})), + ). For(&orcv1alpha1.Trunk{}) if err := errors.Join( @@ -145,6 +175,7 @@ func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ct projectDependency.AddToManager(ctx, mgr), portImportDependency.AddToManager(ctx, mgr), projectImportDependency.AddToManager(ctx, mgr), + subportPortDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/trunk/status.go b/internal/controllers/trunk/status.go index eb11a91a7..8ff307cdc 100644 --- a/internal/controllers/trunk/status.go +++ b/internal/controllers/trunk/status.go @@ -52,10 +52,37 @@ func (trunkStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osReso resourceStatus := orcapplyconfigv1alpha1.TrunkResourceStatus(). WithPortID(osResource.PortID). WithProjectID(osResource.ProjectID). - WithName(osResource.Name) + WithName(osResource.Name). + WithAdminStateUp(osResource.AdminStateUp). + WithRevisionNumber(int64(osResource.RevisionNumber)). + WithCreatedAt(metav1.NewTime(osResource.CreatedAt)). + WithUpdatedAt(metav1.NewTime(osResource.UpdatedAt)) - // TODO(scaffolding): add all of the fields supported in the TrunkResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + if osResource.Status != "" { + resourceStatus.WithStatus(osResource.Status) + } + + if osResource.TenantID != "" { + resourceStatus.WithTenantID(osResource.TenantID) + } + + if len(osResource.Tags) > 0 { + resourceStatus.WithTags(osResource.Tags...) + } + + if len(osResource.Subports) > 0 { + subports := make([]*orcapplyconfigv1alpha1.TrunkSubportStatusApplyConfiguration, 0, len(osResource.Subports)) + for i := range osResource.Subports { + sp := osResource.Subports[i] + subports = append(subports, + orcapplyconfigv1alpha1.TrunkSubportStatus(). + WithPortID(sp.PortID). + WithSegmentationID(int32(sp.SegmentationID)). + WithSegmentationType(sp.SegmentationType), + ) + } + resourceStatus.WithSubports(subports...) + } if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) diff --git a/internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml b/internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml index c3a7df9c9..cac1ea281 100644 --- a/internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-create-full/00-assert.yaml @@ -7,7 +7,11 @@ status: resource: name: trunk-create-full-override description: Trunk from "create full" test - # TODO(scaffolding): Add all fields the resource supports + adminStateUp: false + status: ACTIVE + tags: + - tag1 + - tag2 conditions: - type: Available status: "True" @@ -27,6 +31,14 @@ resourceRefs: kind: Port name: trunk-create-full ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-create-full-subport1 + ref: subport1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-create-full-subport2 + ref: subport2 - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: trunk-create-full @@ -35,4 +47,10 @@ assertAll: - celExpr: "trunk.status.id != ''" - celExpr: "trunk.status.resource.portID == port.status.id" - celExpr: "trunk.status.resource.projectID == project.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "trunk.status.resource.tenantID != ''" + - celExpr: "trunk.status.resource.createdAt != ''" + - celExpr: "trunk.status.resource.updatedAt != ''" + - celExpr: "trunk.status.resource.revisionNumber > 0" + - celExpr: "trunk.status.resource.subports.size() == 2" + - celExpr: "trunk.status.resource.subports.exists(s, s.portID == subport1.status.id && s.segmentationID == 100 && s.segmentationType == 'vlan')" + - celExpr: "trunk.status.resource.subports.exists(s, s.portID == subport2.status.id && s.segmentationID == 200 && s.segmentationType == 'vlan')" diff --git a/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml b/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml index bf1cf03da..52e9e7ab7 100644 --- a/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-create-full/00-create-resource.yaml @@ -1,16 +1,67 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: trunk-create-full +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-create-full +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: trunk-create-full +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-create-full + ipVersion: 4 + cidr: 192.168.158.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port metadata: name: trunk-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-create-full + addresses: + - subnetRef: trunk-create-full +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-create-full-subport1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-create-full +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-create-full-subport2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-create-full --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project @@ -18,11 +69,9 @@ metadata: name: trunk-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -31,13 +80,22 @@ metadata: name: trunk-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: name: trunk-create-full-override description: Trunk from "create full" test + adminStateUp: false portRef: trunk-create-full projectRef: trunk-create-full - # TODO(scaffolding): Add all fields the resource supports + subports: + - portRef: trunk-create-full-subport1 + segmentationID: 100 + segmentationType: vlan + - portRef: trunk-create-full-subport2 + segmentationID: 200 + segmentationType: vlan + tags: + - tag1 + - tag2 diff --git a/internal/controllers/trunk/tests/trunk-create-full/01-assert.yaml b/internal/controllers/trunk/tests/trunk-create-full/01-assert.yaml new file mode 100644 index 000000000..cd2aea264 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-full/01-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-create-full +status: + resource: + adminStateUp: true diff --git a/internal/controllers/trunk/tests/trunk-create-full/01-set-adminstateup.yaml b/internal/controllers/trunk/tests/trunk-create-full/01-set-adminstateup.yaml new file mode 100644 index 000000000..35b1c16d5 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-create-full/01-set-adminstateup.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-create-full +spec: + resource: + adminStateUp: true diff --git a/internal/controllers/trunk/tests/trunk-create-full/README.md b/internal/controllers/trunk/tests/trunk-create-full/README.md index 45eabc565..b09578791 100644 --- a/internal/controllers/trunk/tests/trunk-create-full/README.md +++ b/internal/controllers/trunk/tests/trunk-create-full/README.md @@ -6,6 +6,10 @@ Create a Trunk using all available fields, and verify that the observed state co Also validate that the OpenStack resource uses the name from the spec when it is specified. +## Step 01 + +By default neutron refuses to delete disabled trunks. This step sets the `AdminStateUp` accordingly so that we can delete the trunk. + ## Reference https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml index 029262124..0a90e0c7f 100644 --- a/internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-create-minimal/00-assert.yaml @@ -6,7 +6,8 @@ metadata: status: resource: name: trunk-create-minimal - # TODO(scaffolding): Add all fields the resource supports + adminStateUp: true + status: ACTIVE conditions: - type: Available status: "True" @@ -29,4 +30,11 @@ resourceRefs: assertAll: - celExpr: "trunk.status.id != ''" - celExpr: "trunk.status.resource.portID == port.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "trunk.status.resource.projectID != ''" + - celExpr: "trunk.status.resource.tenantID != ''" + - celExpr: "trunk.status.resource.createdAt != ''" + - celExpr: "trunk.status.resource.updatedAt != ''" + - celExpr: "trunk.status.resource.revisionNumber > 0" + - celExpr: "!has(trunk.status.resource.description)" + - celExpr: "!has(trunk.status.resource.tags)" + - celExpr: "!has(trunk.status.resource.subports)" diff --git a/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml b/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml index 0f61f5c8e..c3cc20990 100644 --- a/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-create-minimal/00-create-resource.yaml @@ -1,16 +1,43 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: trunk-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-create-minimal +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: trunk-create-minimal +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-create-minimal + ipVersion: 4 + cidr: 192.168.156.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port metadata: name: trunk-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-create-minimal + addresses: + - subnetRef: trunk-create-minimal --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -18,11 +45,8 @@ metadata: name: trunk-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: - portRef: trunk-create-full + portRef: trunk-create-minimal diff --git a/internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml index b5b6be241..62cf47bac 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/00-assert.yaml @@ -31,6 +31,21 @@ status: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk +metadata: + name: trunk-dependency-no-subport +status: + conditions: + - type: Available + message: Waiting for Port/trunk-dependency-subport-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Port/trunk-dependency-subport-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk metadata: name: trunk-dependency-no-project status: diff --git a/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml index 11a5ba69f..78a4a4c4c 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/00-create-resources-missing-deps.yaml @@ -1,16 +1,71 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port +kind: Network +metadata: + name: trunk-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet metadata: name: trunk-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + cidr: 192.168.160.0/24 + ipVersion: 4 + networkRef: trunk-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-dependency-parent-no-subport +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-dependency + addresses: + - subnetRef: trunk-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-dependency-parent-no-project +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-dependency + addresses: + - subnetRef: trunk-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-dependency-parent-no-secret +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-dependency + addresses: + - subnetRef: trunk-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -18,28 +73,40 @@ metadata: name: trunk-dependency-no-port spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: portRef: trunk-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the resource --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk metadata: - name: trunk-dependency-no-project + name: trunk-dependency-no-subport spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: - portRef: trunk-dependency + portRef: trunk-dependency-parent-no-subport + subports: + - portRef: trunk-dependency-subport-pending + segmentationID: 100 + segmentationType: vlan +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-dependency-no-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + portRef: trunk-dependency-parent-no-project projectRef: trunk-dependency - # TODO(scaffolding): Add the necessary fields to create the resource --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -47,10 +114,8 @@ metadata: name: trunk-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: trunk-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: - portRef: trunk-dependency + portRef: trunk-dependency-parent-no-secret diff --git a/internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml index c2a36dd02..c7a7be2a2 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/01-assert.yaml @@ -31,6 +31,21 @@ status: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk +metadata: + name: trunk-dependency-no-subport +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: Trunk metadata: name: trunk-dependency-no-project status: diff --git a/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml b/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml index c78751b67..2359632d2 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/01-create-dependencies.yaml @@ -11,12 +11,27 @@ metadata: name: trunk-dependency-pending spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-dependency + addresses: + - subnetRef: trunk-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-dependency-subport-pending +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-dependency + addresses: + - subnetRef: trunk-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project @@ -24,9 +39,7 @@ metadata: name: trunk-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} diff --git a/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml index 7a14ca79c..4b1e7d9e1 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/02-assert.yaml @@ -4,8 +4,12 @@ kind: TestAssert resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port - name: trunk-dependency + name: trunk-dependency-pending ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-dependency-subport-pending + ref: subport - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: trunk-dependency @@ -17,6 +21,8 @@ resourceRefs: assertAll: - celExpr: "port.metadata.deletionTimestamp != 0" - celExpr: "'openstack.k-orc.cloud/trunk' in port.metadata.finalizers" + - celExpr: "subport.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/trunk-subport' in subport.metadata.finalizers" - celExpr: "project.metadata.deletionTimestamp != 0" - celExpr: "'openstack.k-orc.cloud/trunk' in project.metadata.finalizers" - celExpr: "secret.metadata.deletionTimestamp != 0" diff --git a/internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml b/internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml index f2cc3c3f4..0a4b8702f 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/02-delete-dependencies.yaml @@ -3,7 +3,9 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: # We expect the deletion to hang due to the finalizer, so use --wait=false - - command: kubectl delete port.openstack.k-orc.cloud trunk-dependency --wait=false + - command: kubectl delete port.openstack.k-orc.cloud trunk-dependency-pending --wait=false + namespaced: true + - command: kubectl delete port.openstack.k-orc.cloud trunk-dependency-subport-pending --wait=false namespaced: true - command: kubectl delete project.openstack.k-orc.cloud trunk-dependency --wait=false namespaced: true diff --git a/internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml b/internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml index c408c33b8..c334998c2 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/03-assert.yaml @@ -3,7 +3,9 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: # Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get port.openstack.k-orc.cloud trunk-dependency --namespace $NAMESPACE" +- script: "! kubectl get port.openstack.k-orc.cloud trunk-dependency-pending --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get port.openstack.k-orc.cloud trunk-dependency-subport-pending --namespace $NAMESPACE" skipLogOutput: true - script: "! kubectl get project.openstack.k-orc.cloud trunk-dependency --namespace $NAMESPACE" skipLogOutput: true diff --git a/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml b/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml index 25f2a80c5..c54f12587 100644 --- a/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml +++ b/internal/controllers/trunk/tests/trunk-dependency/03-delete-resources.yaml @@ -8,6 +8,9 @@ delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk name: trunk-dependency-no-port +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-dependency-no-subport - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk name: trunk-dependency-no-project diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml index 2cf348d34..4f8c0bc59 100644 --- a/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-import-dependency/00-import-resource.yaml @@ -18,7 +18,7 @@ metadata: name: trunk-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -31,7 +31,7 @@ metadata: name: trunk-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml index b5aeb8971..0289e6da8 100644 --- a/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-import-dependency/01-create-trap-resource.yaml @@ -1,29 +1,29 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port +kind: Network metadata: - name: trunk-import-dependency-not-this-one + name: trunk-import-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: trunk-import-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project +kind: Subnet metadata: - name: trunk-import-dependency-not-this-one + name: trunk-import-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-import-dependency + ipVersion: 4 + cidr: 192.168.156.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port @@ -31,11 +31,23 @@ metadata: name: trunk-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource + resource: + networkRef: trunk-import-dependency + addresses: + - subnetRef: trunk-import-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: trunk-import-dependency-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed resource: {} --- # This `trunk-import-dependency-not-this-one` should not be picked by the import filter @@ -45,12 +57,9 @@ metadata: name: trunk-import-dependency-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - portRef: trunk-import-dependency-not-this-one portRef: trunk-import-dependency-not-this-one projectRef: trunk-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml b/internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml index ca57bcc93..549de2b13 100644 --- a/internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-import-dependency/02-create-resource.yaml @@ -5,25 +5,13 @@ metadata: name: trunk-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port -metadata: - name: trunk-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-import-dependency + addresses: + - subnetRef: trunk-import-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project @@ -31,11 +19,9 @@ metadata: name: trunk-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: {} --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -44,12 +30,9 @@ metadata: name: trunk-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - portRef: trunk-import-dependency-external portRef: trunk-import-dependency-external projectRef: trunk-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml b/internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml index fa08cde79..03f2ccd34 100644 --- a/internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml +++ b/internal/controllers/trunk/tests/trunk-import-error/00-create-resources.yaml @@ -1,16 +1,57 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port +kind: Network +metadata: + name: trunk-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-import-error +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet metadata: name: trunk-import-error spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-import-error + ipVersion: 4 + cidr: 192.168.156.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-error-1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-import-error + addresses: + - subnetRef: trunk-import-error +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-import-error-2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-import-error + addresses: + - subnetRef: trunk-import-error --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -18,14 +59,12 @@ metadata: name: trunk-import-error-external-1 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: description: Trunk from "import error" test - portRef: trunk-import-error - # TODO(scaffolding): add any required field + portRef: trunk-import-error-1 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -33,11 +72,9 @@ metadata: name: trunk-import-error-external-2 spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: description: Trunk from "import error" test - portRef: trunk-import-error - # TODO(scaffolding): add any required field + portRef: trunk-import-error-2 diff --git a/internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml b/internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml index 787ad1312..9bb9fa452 100644 --- a/internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-import/00-import-resource.yaml @@ -12,4 +12,6 @@ spec: filter: name: trunk-import-external description: Trunk trunk-import-external from "trunk-import" test - # TODO(scaffolding): Add all fields supported by the filter + adminStateUp: true + tags: + - trunk-import-tag diff --git a/internal/controllers/trunk/tests/trunk-import/01-assert.yaml b/internal/controllers/trunk/tests/trunk-import/01-assert.yaml index ed077e4ba..7ae99dc6b 100644 --- a/internal/controllers/trunk/tests/trunk-import/01-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-import/01-assert.yaml @@ -16,7 +16,9 @@ status: resource: name: trunk-import-external-not-this-one description: Trunk trunk-import-external from "trunk-import" test - # TODO(scaffolding): Add fields necessary to match filter + adminStateUp: true + tags: + - trunk-import-tag --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk diff --git a/internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml b/internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml index 05ea99726..df57a1f5f 100644 --- a/internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-import/01-create-trap-resource.yaml @@ -1,16 +1,43 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: trunk-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-import +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: trunk-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-import + ipVersion: 4 + cidr: 192.168.156.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port metadata: name: trunk-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-import + addresses: + - subnetRef: trunk-import --- # This `trunk-import-external-not-this-one` resource serves two purposes: # - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) @@ -21,11 +48,12 @@ metadata: name: trunk-import-external-not-this-one spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: description: Trunk trunk-import-external from "trunk-import" test portRef: trunk-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter + adminStateUp: true + tags: + - trunk-import-tag diff --git a/internal/controllers/trunk/tests/trunk-import/02-assert.yaml b/internal/controllers/trunk/tests/trunk-import/02-assert.yaml index 607841b87..b56a2d745 100644 --- a/internal/controllers/trunk/tests/trunk-import/02-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-import/02-assert.yaml @@ -30,4 +30,6 @@ status: resource: name: trunk-import-external description: Trunk trunk-import-external from "trunk-import" test - # TODO(scaffolding): Add all fields the resource supports + adminStateUp: true + tags: + - trunk-import-tag diff --git a/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml b/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml index 2596a4bf5..7ce4ff50e 100644 --- a/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-import/02-create-resource.yaml @@ -5,12 +5,13 @@ metadata: name: trunk-import spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-import + addresses: + - subnetRef: trunk-import --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -18,11 +19,12 @@ metadata: name: trunk-import-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: description: Trunk trunk-import-external from "trunk-import" test portRef: trunk-import - # TODO(scaffolding): Add fields necessary to match filter + adminStateUp: true + tags: + - trunk-import-tag diff --git a/internal/controllers/trunk/tests/trunk-update/00-assert.yaml b/internal/controllers/trunk/tests/trunk-update/00-assert.yaml index f241702a3..b6701deb0 100644 --- a/internal/controllers/trunk/tests/trunk-update/00-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-update/00-assert.yaml @@ -1,14 +1,4 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Trunk - name: trunk-update - ref: trunk -assertAll: - - celExpr: "!has(trunk.status.resource.description)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk metadata: @@ -16,7 +6,8 @@ metadata: status: resource: name: trunk-update - # TODO(scaffolding): Add matches for more fields + adminStateUp: true + status: ACTIVE conditions: - type: Available status: "True" @@ -24,3 +15,26 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-update + ref: trunk + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-update + ref: port +assertAll: + - celExpr: "trunk.status.id != ''" + - celExpr: "trunk.status.resource.portID == port.status.id" + - celExpr: "trunk.status.resource.projectID != ''" + - celExpr: "trunk.status.resource.tenantID != ''" + - celExpr: "trunk.status.resource.createdAt != ''" + - celExpr: "trunk.status.resource.updatedAt != ''" + - celExpr: "trunk.status.resource.revisionNumber > 0" + - celExpr: "!has(trunk.status.resource.description)" + - celExpr: "!has(trunk.status.resource.tags)" + - celExpr: "!has(trunk.status.resource.subports)" diff --git a/internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml b/internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml index 1226197a8..150368865 100644 --- a/internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-update/00-minimal-resource.yaml @@ -1,16 +1,43 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: trunk-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: trunk-update +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: trunk-update +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-update + ipVersion: 4 + cidr: 192.168.156.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port metadata: name: trunk-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: trunk-update + addresses: + - subnetRef: trunk-update --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk @@ -18,11 +45,8 @@ metadata: name: trunk-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resouce needs admin credentials to be created or updated cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: portRef: trunk-update diff --git a/internal/controllers/trunk/tests/trunk-update/01-assert.yaml b/internal/controllers/trunk/tests/trunk-update/01-assert.yaml index 0750bc9ce..3d5aaaffc 100644 --- a/internal/controllers/trunk/tests/trunk-update/01-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-update/01-assert.yaml @@ -7,7 +7,14 @@ status: resource: name: trunk-update-updated description: trunk-update-updated - # TODO(scaffolding): match all fields that were modified + adminStateUp: true + status: ACTIVE + tags: + - tag1 + - tag2 + subports: + - segmentationID: 100 + segmentationType: vlan conditions: - type: Available status: "True" @@ -15,3 +22,29 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Trunk + name: trunk-update + ref: trunk + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-update + ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-update-subport + ref: subport +assertAll: + - celExpr: "trunk.status.id != ''" + - celExpr: "trunk.status.resource.portID == port.status.id" + - celExpr: "trunk.status.resource.projectID != ''" + - celExpr: "trunk.status.resource.tenantID != ''" + - celExpr: "trunk.status.resource.createdAt != ''" + - celExpr: "trunk.status.resource.updatedAt != ''" + - celExpr: "trunk.status.resource.revisionNumber > 0" + - celExpr: "trunk.status.resource.subports.size() == 1" + - celExpr: "trunk.status.resource.subports[0].portID == subport.status.id" diff --git a/internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml b/internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml index 4d9937eb0..1968abbc4 100644 --- a/internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml +++ b/internal/controllers/trunk/tests/trunk-update/01-updated-resource.yaml @@ -1,5 +1,17 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: trunk-update-subport +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: trunk-update +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk metadata: name: trunk-update @@ -7,4 +19,10 @@ spec: resource: name: trunk-update-updated description: trunk-update-updated - # TODO(scaffolding): update all mutable fields + subports: + - portRef: trunk-update-subport + segmentationID: 100 + segmentationType: vlan + tags: + - tag1 + - tag2 diff --git a/internal/controllers/trunk/tests/trunk-update/02-assert.yaml b/internal/controllers/trunk/tests/trunk-update/02-assert.yaml index 33096164f..a29e0774d 100644 --- a/internal/controllers/trunk/tests/trunk-update/02-assert.yaml +++ b/internal/controllers/trunk/tests/trunk-update/02-assert.yaml @@ -1,22 +1,11 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Trunk - name: trunk-update - ref: trunk -assertAll: - - celExpr: "!has(trunk.status.resource.description)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Trunk metadata: name: trunk-update status: resource: - name: trunk-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + adminStateUp: false conditions: - type: Available status: "True" diff --git a/internal/controllers/trunk/tests/trunk-update/02-disable-trunk.yaml b/internal/controllers/trunk/tests/trunk-update/02-disable-trunk.yaml new file mode 100644 index 000000000..651030891 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/02-disable-trunk.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +spec: + resource: + adminStateUp: false diff --git a/internal/controllers/trunk/tests/trunk-update/03-assert.yaml b/internal/controllers/trunk/tests/trunk-update/03-assert.yaml new file mode 100644 index 000000000..9339079bc --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/03-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +status: + resource: + adminStateUp: true + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/trunk/tests/trunk-update/03-enable-trunk.yaml b/internal/controllers/trunk/tests/trunk-update/03-enable-trunk.yaml new file mode 100644 index 000000000..cc43865cf --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/03-enable-trunk.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +spec: + resource: + adminStateUp: true diff --git a/internal/controllers/trunk/tests/trunk-update/04-assert.yaml b/internal/controllers/trunk/tests/trunk-update/04-assert.yaml new file mode 100644 index 000000000..b6701deb0 --- /dev/null +++ b/internal/controllers/trunk/tests/trunk-update/04-assert.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Trunk +metadata: + name: trunk-update +status: + resource: + name: trunk-update + adminStateUp: true + status: ACTIVE + 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: Trunk + name: trunk-update + ref: trunk + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: trunk-update + ref: port +assertAll: + - celExpr: "trunk.status.id != ''" + - celExpr: "trunk.status.resource.portID == port.status.id" + - celExpr: "trunk.status.resource.projectID != ''" + - celExpr: "trunk.status.resource.tenantID != ''" + - celExpr: "trunk.status.resource.createdAt != ''" + - celExpr: "trunk.status.resource.updatedAt != ''" + - celExpr: "trunk.status.resource.revisionNumber > 0" + - celExpr: "!has(trunk.status.resource.description)" + - celExpr: "!has(trunk.status.resource.tags)" + - celExpr: "!has(trunk.status.resource.subports)" diff --git a/internal/controllers/trunk/tests/trunk-update/02-reverted-resource.yaml b/internal/controllers/trunk/tests/trunk-update/04-reverted-resource.yaml similarity index 100% rename from internal/controllers/trunk/tests/trunk-update/02-reverted-resource.yaml rename to internal/controllers/trunk/tests/trunk-update/04-reverted-resource.yaml diff --git a/internal/controllers/trunk/tests/trunk-update/README.md b/internal/controllers/trunk/tests/trunk-update/README.md index a7040db70..1f6cf231a 100644 --- a/internal/controllers/trunk/tests/trunk-update/README.md +++ b/internal/controllers/trunk/tests/trunk-update/README.md @@ -6,10 +6,18 @@ Create a Trunk using only mandatory fields. ## Step 01 -Update all mutable fields. +Update all mutable fields, except for `AdminStateUp`, since neutron disallow operations on disabled trunks. ## Step 02 +Update `AdminStateUp`. + +## Step 03 + +Re-enable the trunk by setting `AdminStateUp` to `true`. This must be done before reverting the resource, since neutron disallows operations on disabled trunks. + +## Step 04 + Revert the resource to its original value and verify that the resulting object matches its state when first created. ## Reference diff --git a/internal/controllers/trunk/zz_generated.adapter.go b/internal/controllers/trunk/zz_generated.adapter.go new file mode 100644 index 000000000..ef7e54457 --- /dev/null +++ b/internal/controllers/trunk/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 trunk + +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.Trunk + orcObjectListT = orcv1alpha1.TrunkList + resourceSpecT = orcv1alpha1.TrunkResourceSpec + filterT = orcv1alpha1.TrunkFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = trunkAdapter +) + +type trunkAdapter struct { + *orcv1alpha1.Trunk +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.Trunk +} + +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/trunk/zz_generated.controller.go b/internal/controllers/trunk/zz_generated.controller.go new file mode 100644 index 000000000..6b36c15cc --- /dev/null +++ b/internal/controllers/trunk/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 trunk + +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/networking.go b/internal/osclients/mock/networking.go index ceab233d2..deaa04f70 100644 --- a/internal/osclients/mock/networking.go +++ b/internal/osclients/mock/networking.go @@ -34,6 +34,7 @@ import ( routers "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/routers" groups "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups" rules "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" + trunks "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/trunks" networks "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" ports "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" subnets "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets" @@ -80,6 +81,21 @@ func (mr *MockNetworkClientMockRecorder) AddRouterInterface(ctx, id, opts any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRouterInterface", reflect.TypeOf((*MockNetworkClient)(nil).AddRouterInterface), ctx, id, opts) } +// AddSubports mocks base method. +func (m *MockNetworkClient) AddSubports(ctx context.Context, id string, opts trunks.AddSubportsOptsBuilder) (*trunks.Trunk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddSubports", ctx, id, opts) + ret0, _ := ret[0].(*trunks.Trunk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddSubports indicates an expected call of AddSubports. +func (mr *MockNetworkClientMockRecorder) AddSubports(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubports", reflect.TypeOf((*MockNetworkClient)(nil).AddSubports), ctx, id, opts) +} + // CreateFloatingIP mocks base method. func (m *MockNetworkClient) CreateFloatingIP(ctx context.Context, opts floatingips.CreateOptsBuilder) (*floatingips.FloatingIP, error) { m.ctrl.T.Helper() @@ -185,6 +201,21 @@ func (mr *MockNetworkClientMockRecorder) CreateSubnet(ctx, opts any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSubnet", reflect.TypeOf((*MockNetworkClient)(nil).CreateSubnet), ctx, opts) } +// CreateTrunk mocks base method. +func (m *MockNetworkClient) CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateTrunk", ctx, opts) + ret0, _ := ret[0].(*trunks.Trunk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateTrunk indicates an expected call of CreateTrunk. +func (mr *MockNetworkClientMockRecorder) CreateTrunk(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTrunk", reflect.TypeOf((*MockNetworkClient)(nil).CreateTrunk), ctx, opts) +} + // DeleteFloatingIP mocks base method. func (m *MockNetworkClient) DeleteFloatingIP(ctx context.Context, id string) error { m.ctrl.T.Helper() @@ -283,6 +314,20 @@ func (mr *MockNetworkClientMockRecorder) DeleteSubnet(ctx, id any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSubnet", reflect.TypeOf((*MockNetworkClient)(nil).DeleteSubnet), ctx, id) } +// DeleteTrunk mocks base method. +func (m *MockNetworkClient) DeleteTrunk(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteTrunk", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteTrunk indicates an expected call of DeleteTrunk. +func (mr *MockNetworkClientMockRecorder) DeleteTrunk(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTrunk", reflect.TypeOf((*MockNetworkClient)(nil).DeleteTrunk), ctx, resourceID) +} + // GetFloatingIP mocks base method. func (m *MockNetworkClient) GetFloatingIP(ctx context.Context, id string) (*floatingips.FloatingIP, error) { m.ctrl.T.Helper() @@ -388,6 +433,21 @@ func (mr *MockNetworkClientMockRecorder) GetSubnet(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnet", reflect.TypeOf((*MockNetworkClient)(nil).GetSubnet), ctx, id) } +// GetTrunk mocks base method. +func (m *MockNetworkClient) GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTrunk", ctx, resourceID) + ret0, _ := ret[0].(*trunks.Trunk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTrunk indicates an expected call of GetTrunk. +func (mr *MockNetworkClientMockRecorder) GetTrunk(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTrunk", reflect.TypeOf((*MockNetworkClient)(nil).GetTrunk), ctx, resourceID) +} + // ListFloatingIP mocks base method. func (m *MockNetworkClient) ListFloatingIP(ctx context.Context, opts floatingips.ListOptsBuilder) iter.Seq2[*floatingips.FloatingIP, error] { m.ctrl.T.Helper() @@ -487,6 +547,20 @@ func (mr *MockNetworkClientMockRecorder) ListSubnet(ctx, opts any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSubnet", reflect.TypeOf((*MockNetworkClient)(nil).ListSubnet), ctx, opts) } +// ListTrunks mocks base method. +func (m *MockNetworkClient) ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListTrunks", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*trunks.Trunk, error]) + return ret0 +} + +// ListTrunks indicates an expected call of ListTrunks. +func (mr *MockNetworkClientMockRecorder) ListTrunks(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTrunks", reflect.TypeOf((*MockNetworkClient)(nil).ListTrunks), ctx, listOpts) +} + // RemoveRouterInterface mocks base method. func (m *MockNetworkClient) RemoveRouterInterface(ctx context.Context, id string, opts routers.RemoveInterfaceOptsBuilder) (*routers.InterfaceInfo, error) { m.ctrl.T.Helper() @@ -502,6 +576,20 @@ func (mr *MockNetworkClientMockRecorder) RemoveRouterInterface(ctx, id, opts any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveRouterInterface", reflect.TypeOf((*MockNetworkClient)(nil).RemoveRouterInterface), ctx, id, opts) } +// RemoveSubports mocks base method. +func (m *MockNetworkClient) RemoveSubports(ctx context.Context, id string, opts trunks.RemoveSubportsOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveSubports", ctx, id, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveSubports indicates an expected call of RemoveSubports. +func (mr *MockNetworkClientMockRecorder) RemoveSubports(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveSubports", reflect.TypeOf((*MockNetworkClient)(nil).RemoveSubports), ctx, id, opts) +} + // ReplaceAllAttributesTags mocks base method. func (m *MockNetworkClient) ReplaceAllAttributesTags(ctx context.Context, resourceType, resourceID string, opts attributestags.ReplaceAllOptsBuilder) ([]string, error) { m.ctrl.T.Helper() @@ -606,3 +694,18 @@ func (mr *MockNetworkClientMockRecorder) UpdateSubnet(ctx, id, opts any) *gomock mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSubnet", reflect.TypeOf((*MockNetworkClient)(nil).UpdateSubnet), ctx, id, opts) } + +// UpdateTrunk mocks base method. +func (m *MockNetworkClient) UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateTrunk", ctx, id, opts) + ret0, _ := ret[0].(*trunks.Trunk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateTrunk indicates an expected call of UpdateTrunk. +func (mr *MockNetworkClientMockRecorder) UpdateTrunk(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTrunk", reflect.TypeOf((*MockNetworkClient)(nil).UpdateTrunk), ctx, id, opts) +} diff --git a/internal/osclients/networking.go b/internal/osclients/networking.go index 99156d64e..8696cdbec 100644 --- a/internal/osclients/networking.go +++ b/internal/osclients/networking.go @@ -102,6 +102,14 @@ type NetworkClient interface { GetSubnet(ctx context.Context, id string) (*subnets.Subnet, error) UpdateSubnet(ctx context.Context, id string, opts subnets.UpdateOptsBuilder) (*subnets.Subnet, error) + ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] + CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) + DeleteTrunk(ctx context.Context, resourceID string) error + GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) + UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) + AddSubports(ctx context.Context, id string, opts trunks.AddSubportsOptsBuilder) (*trunks.Trunk, error) + RemoveSubports(ctx context.Context, id string, opts trunks.RemoveSubportsOpts) error + ReplaceAllAttributesTags(ctx context.Context, resourceType string, resourceID string, opts attributestags.ReplaceAllOptsBuilder) ([]string, error) } @@ -214,31 +222,6 @@ func (c networkClient) UpdatePort(ctx context.Context, id string, opts ports.Upd return &portExt, nil } -func (c networkClient) CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) { - return trunks.Create(ctx, c.serviceClient, opts).Extract() -} - -func (c networkClient) DeleteTrunk(ctx context.Context, id string) error { - return trunks.Delete(ctx, c.serviceClient, id).ExtractErr() -} - -func (c networkClient) ListTrunkSubports(ctx context.Context, trunkID string) ([]trunks.Subport, error) { - return trunks.GetSubports(ctx, c.serviceClient, trunkID).Extract() -} - -func (c networkClient) RemoveSubports(ctx context.Context, id string, opts trunks.RemoveSubportsOpts) error { - _, err := trunks.RemoveSubports(ctx, c.serviceClient, id, opts).Extract() - return err -} - -func (c networkClient) ListTrunk(ctx context.Context, opts trunks.ListOptsBuilder) ([]trunks.Trunk, error) { - allPages, err := trunks.List(c.serviceClient, opts).AllPages(ctx) - if err != nil { - return nil, err - } - return trunks.ExtractTrunks(allPages) -} - func (c networkClient) CreateRouter(ctx context.Context, opts routers.CreateOptsBuilder) (*routers.Router, error) { return routers.Create(ctx, c.serviceClient, opts).Extract() } @@ -372,3 +355,35 @@ func (c networkClient) ListExtensions(ctx context.Context) ([]extensions.Extensi } return extensions.ExtractExtensions(allPages) } + +func (c networkClient) ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] { + pager := trunks.List(c.serviceClient, listOpts) + return func(yield func(*trunks.Trunk, error) bool) { + _ = pager.EachPage(ctx, yieldPage(trunks.ExtractTrunks, yield)) + } +} + +func (c networkClient) CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) { + return trunks.Create(ctx, c.serviceClient, opts).Extract() +} + +func (c networkClient) DeleteTrunk(ctx context.Context, resourceID string) error { + return trunks.Delete(ctx, c.serviceClient, resourceID).ExtractErr() +} + +func (c networkClient) GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) { + return trunks.Get(ctx, c.serviceClient, resourceID).Extract() +} + +func (c networkClient) UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) { + return trunks.Update(ctx, c.serviceClient, id, opts).Extract() +} + +func (c networkClient) AddSubports(ctx context.Context, id string, opts trunks.AddSubportsOptsBuilder) (*trunks.Trunk, error) { + return trunks.AddSubports(ctx, c.serviceClient, id, opts).Extract() +} + +func (c networkClient) RemoveSubports(ctx context.Context, id string, opts trunks.RemoveSubportsOpts) error { + _, err := trunks.RemoveSubports(ctx, c.serviceClient, id, opts).Extract() + return err +} diff --git a/internal/osclients/trunk.go b/internal/osclients/trunk.go deleted file mode 100644 index cfa3db90f..000000000 --- a/internal/osclients/trunk.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -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/trunks" - "github.com/gophercloud/utils/v2/openstack/clientconfig" -) - -type TrunkClient interface { - ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] - CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) - DeleteTrunk(ctx context.Context, resourceID string) error - GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) - UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) -} - -type trunkClient struct{ client *gophercloud.ServiceClient } - -// NewTrunkClient returns a new OpenStack client. -func NewTrunkClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (TrunkClient, 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 trunk service client: %v", err) - } - - return &trunkClient{client}, nil -} - -func (c trunkClient) ListTrunks(ctx context.Context, listOpts trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] { - pager := trunks.List(c.client, listOpts) - return func(yield func(*trunks.Trunk, error) bool) { - _ = pager.EachPage(ctx, yieldPage(trunks.ExtractTrunks, yield)) - } -} - -func (c trunkClient) CreateTrunk(ctx context.Context, opts trunks.CreateOptsBuilder) (*trunks.Trunk, error) { - return trunks.Create(ctx, c.client, opts).Extract() -} - -func (c trunkClient) DeleteTrunk(ctx context.Context, resourceID string) error { - return trunks.Delete(ctx, c.client, resourceID).ExtractErr() -} - -func (c trunkClient) GetTrunk(ctx context.Context, resourceID string) (*trunks.Trunk, error) { - return trunks.Get(ctx, c.client, resourceID).Extract() -} - -func (c trunkClient) UpdateTrunk(ctx context.Context, id string, opts trunks.UpdateOptsBuilder) (*trunks.Trunk, error) { - return trunks.Update(ctx, c.client, id, opts).Extract() -} - -type trunkErrorClient struct{ error } - -// NewTrunkErrorClient returns a TrunkClient in which every method returns the given error. -func NewTrunkErrorClient(e error) TrunkClient { - return trunkErrorClient{e} -} - -func (e trunkErrorClient) ListTrunks(_ context.Context, _ trunks.ListOptsBuilder) iter.Seq2[*trunks.Trunk, error] { - return func(yield func(*trunks.Trunk, error) bool) { - yield(nil, e.error) - } -} - -func (e trunkErrorClient) CreateTrunk(_ context.Context, _ trunks.CreateOptsBuilder) (*trunks.Trunk, error) { - return nil, e.error -} - -func (e trunkErrorClient) DeleteTrunk(_ context.Context, _ string) error { - return e.error -} - -func (e trunkErrorClient) GetTrunk(_ context.Context, _ string) (*trunks.Trunk, error) { - return nil, e.error -} - -func (e trunkErrorClient) UpdateTrunk(_ context.Context, _ string, _ trunks.UpdateOptsBuilder) (*trunks.Trunk, error) { - return nil, e.error -} diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d499782e6..67828d420 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -19,6 +19,7 @@ testDirs: - ./internal/controllers/servergroup/tests/ - ./internal/controllers/service/tests/ - ./internal/controllers/subnet/tests/ +- ./internal/controllers/trunk/tests/ - ./internal/controllers/volume/tests/ - ./internal/controllers/volumetype/tests/ timeout: 240 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunk.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunk.go new file mode 100644 index 000000000..60ee92b13 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunk.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" +) + +// TrunkApplyConfiguration represents a declarative configuration of the Trunk type for use +// with apply. +type TrunkApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *TrunkSpecApplyConfiguration `json:"spec,omitempty"` + Status *TrunkStatusApplyConfiguration `json:"status,omitempty"` +} + +// Trunk constructs a declarative configuration of the Trunk type for use with +// apply. +func Trunk(name, namespace string) *TrunkApplyConfiguration { + b := &TrunkApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Trunk") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractTrunk extracts the applied configuration owned by fieldManager from +// trunk. If no managedFields are found in trunk for fieldManager, a +// TrunkApplyConfiguration 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. +// trunk must be a unmodified Trunk API object that was retrieved from the Kubernetes API. +// ExtractTrunk 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 ExtractTrunk(trunk *apiv1alpha1.Trunk, fieldManager string) (*TrunkApplyConfiguration, error) { + return extractTrunk(trunk, fieldManager, "") +} + +// ExtractTrunkStatus is the same as ExtractTrunk except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractTrunkStatus(trunk *apiv1alpha1.Trunk, fieldManager string) (*TrunkApplyConfiguration, error) { + return extractTrunk(trunk, fieldManager, "status") +} + +func extractTrunk(trunk *apiv1alpha1.Trunk, fieldManager string, subresource string) (*TrunkApplyConfiguration, error) { + b := &TrunkApplyConfiguration{} + err := managedfields.ExtractInto(trunk, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Trunk"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(trunk.Name) + b.WithNamespace(trunk.Namespace) + + b.WithKind("Trunk") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b TrunkApplyConfiguration) 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 *TrunkApplyConfiguration) WithKind(value string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithAPIVersion(value string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithName(value string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithGenerateName(value string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithNamespace(value string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithUID(value types.UID) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithResourceVersion(value string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithGeneration(value int64) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithCreationTimestamp(value metav1.Time) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithLabels(entries map[string]string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithAnnotations(entries map[string]string) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithFinalizers(values ...string) *TrunkApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *TrunkApplyConfiguration) 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 *TrunkApplyConfiguration) WithSpec(value *TrunkSpecApplyConfiguration) *TrunkApplyConfiguration { + 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 *TrunkApplyConfiguration) WithStatus(value *TrunkStatusApplyConfiguration) *TrunkApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *TrunkApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *TrunkApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *TrunkApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *TrunkApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkfilter.go new file mode 100644 index 000000000..e6efbaa32 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkfilter.go @@ -0,0 +1,120 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// TrunkFilterApplyConfiguration represents a declarative configuration of the TrunkFilter type for use +// with apply. +type TrunkFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` + PortRef *apiv1alpha1.KubernetesNameRef `json:"portRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + FilterByNeutronTagsApplyConfiguration `json:",inline"` +} + +// TrunkFilterApplyConfiguration constructs a declarative configuration of the TrunkFilter type for use with +// apply. +func TrunkFilter() *TrunkFilterApplyConfiguration { + return &TrunkFilterApplyConfiguration{} +} + +// 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 *TrunkFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *TrunkFilterApplyConfiguration { + 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 *TrunkFilterApplyConfiguration) WithDescription(value apiv1alpha1.NeutronDescription) *TrunkFilterApplyConfiguration { + b.Description = &value + return b +} + +// WithPortRef sets the PortRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PortRef field is set to the value of the last call. +func (b *TrunkFilterApplyConfiguration) WithPortRef(value apiv1alpha1.KubernetesNameRef) *TrunkFilterApplyConfiguration { + b.PortRef = &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 *TrunkFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *TrunkFilterApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdminStateUp field is set to the value of the last call. +func (b *TrunkFilterApplyConfiguration) WithAdminStateUp(value bool) *TrunkFilterApplyConfiguration { + b.AdminStateUp = &value + return b +} + +// WithTags adds the given value to the Tags field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Tags field. +func (b *TrunkFilterApplyConfiguration) WithTags(values ...apiv1alpha1.NeutronTag) *TrunkFilterApplyConfiguration { + 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 *TrunkFilterApplyConfiguration) WithTagsAny(values ...apiv1alpha1.NeutronTag) *TrunkFilterApplyConfiguration { + 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 *TrunkFilterApplyConfiguration) WithNotTags(values ...apiv1alpha1.NeutronTag) *TrunkFilterApplyConfiguration { + 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 *TrunkFilterApplyConfiguration) WithNotTagsAny(values ...apiv1alpha1.NeutronTag) *TrunkFilterApplyConfiguration { + for i := range values { + b.FilterByNeutronTagsApplyConfiguration.NotTagsAny = append(b.FilterByNeutronTagsApplyConfiguration.NotTagsAny, values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkimport.go new file mode 100644 index 000000000..960ff678d --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkimport.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 + +// TrunkImportApplyConfiguration represents a declarative configuration of the TrunkImport type for use +// with apply. +type TrunkImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *TrunkFilterApplyConfiguration `json:"filter,omitempty"` +} + +// TrunkImportApplyConfiguration constructs a declarative configuration of the TrunkImport type for use with +// apply. +func TrunkImport() *TrunkImportApplyConfiguration { + return &TrunkImportApplyConfiguration{} +} + +// 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 *TrunkImportApplyConfiguration) WithID(value string) *TrunkImportApplyConfiguration { + 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 *TrunkImportApplyConfiguration) WithFilter(value *TrunkFilterApplyConfiguration) *TrunkImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcespec.go new file mode 100644 index 000000000..5dbdf2846 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcespec.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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// TrunkResourceSpecApplyConfiguration represents a declarative configuration of the TrunkResourceSpec type for use +// with apply. +type TrunkResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` + PortRef *apiv1alpha1.KubernetesNameRef `json:"portRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + Subports []TrunkSubportSpecApplyConfiguration `json:"subports,omitempty"` + Tags []apiv1alpha1.NeutronTag `json:"tags,omitempty"` +} + +// TrunkResourceSpecApplyConfiguration constructs a declarative configuration of the TrunkResourceSpec type for use with +// apply. +func TrunkResourceSpec() *TrunkResourceSpecApplyConfiguration { + return &TrunkResourceSpecApplyConfiguration{} +} + +// 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 *TrunkResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *TrunkResourceSpecApplyConfiguration { + 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 *TrunkResourceSpecApplyConfiguration) WithDescription(value apiv1alpha1.NeutronDescription) *TrunkResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithPortRef sets the PortRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PortRef field is set to the value of the last call. +func (b *TrunkResourceSpecApplyConfiguration) WithPortRef(value apiv1alpha1.KubernetesNameRef) *TrunkResourceSpecApplyConfiguration { + b.PortRef = &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 *TrunkResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *TrunkResourceSpecApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdminStateUp field is set to the value of the last call. +func (b *TrunkResourceSpecApplyConfiguration) WithAdminStateUp(value bool) *TrunkResourceSpecApplyConfiguration { + b.AdminStateUp = &value + return b +} + +// WithSubports adds the given value to the Subports 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 Subports field. +func (b *TrunkResourceSpecApplyConfiguration) WithSubports(values ...*TrunkSubportSpecApplyConfiguration) *TrunkResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSubports") + } + b.Subports = append(b.Subports, *values[i]) + } + return b +} + +// WithTags adds the given value to the Tags field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Tags field. +func (b *TrunkResourceSpecApplyConfiguration) WithTags(values ...apiv1alpha1.NeutronTag) *TrunkResourceSpecApplyConfiguration { + for i := range values { + b.Tags = append(b.Tags, values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcestatus.go new file mode 100644 index 000000000..1904b9fa4 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkresourcestatus.go @@ -0,0 +1,147 @@ +/* +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/apimachinery/pkg/apis/meta/v1" +) + +// TrunkResourceStatusApplyConfiguration represents a declarative configuration of the TrunkResourceStatus type for use +// with apply. +type TrunkResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + PortID *string `json:"portID,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + TenantID *string `json:"tenantID,omitempty"` + Status *string `json:"status,omitempty"` + Tags []string `json:"tags,omitempty"` + NeutronStatusMetadataApplyConfiguration `json:",inline"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + Subports []TrunkSubportStatusApplyConfiguration `json:"subports,omitempty"` +} + +// TrunkResourceStatusApplyConfiguration constructs a declarative configuration of the TrunkResourceStatus type for use with +// apply. +func TrunkResourceStatus() *TrunkResourceStatusApplyConfiguration { + return &TrunkResourceStatusApplyConfiguration{} +} + +// 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 *TrunkResourceStatusApplyConfiguration) WithName(value string) *TrunkResourceStatusApplyConfiguration { + 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 *TrunkResourceStatusApplyConfiguration) WithDescription(value string) *TrunkResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithPortID sets the PortID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PortID field is set to the value of the last call. +func (b *TrunkResourceStatusApplyConfiguration) WithPortID(value string) *TrunkResourceStatusApplyConfiguration { + b.PortID = &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 *TrunkResourceStatusApplyConfiguration) WithProjectID(value string) *TrunkResourceStatusApplyConfiguration { + b.ProjectID = &value + return b +} + +// WithTenantID sets the TenantID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TenantID field is set to the value of the last call. +func (b *TrunkResourceStatusApplyConfiguration) WithTenantID(value string) *TrunkResourceStatusApplyConfiguration { + b.TenantID = &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 *TrunkResourceStatusApplyConfiguration) WithStatus(value string) *TrunkResourceStatusApplyConfiguration { + b.Status = &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 *TrunkResourceStatusApplyConfiguration) WithTags(values ...string) *TrunkResourceStatusApplyConfiguration { + 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 *TrunkResourceStatusApplyConfiguration) WithCreatedAt(value v1.Time) *TrunkResourceStatusApplyConfiguration { + 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 *TrunkResourceStatusApplyConfiguration) WithUpdatedAt(value v1.Time) *TrunkResourceStatusApplyConfiguration { + 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 *TrunkResourceStatusApplyConfiguration) WithRevisionNumber(value int64) *TrunkResourceStatusApplyConfiguration { + b.NeutronStatusMetadataApplyConfiguration.RevisionNumber = &value + return b +} + +// WithAdminStateUp sets the AdminStateUp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdminStateUp field is set to the value of the last call. +func (b *TrunkResourceStatusApplyConfiguration) WithAdminStateUp(value bool) *TrunkResourceStatusApplyConfiguration { + b.AdminStateUp = &value + return b +} + +// WithSubports adds the given value to the Subports 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 Subports field. +func (b *TrunkResourceStatusApplyConfiguration) WithSubports(values ...*TrunkSubportStatusApplyConfiguration) *TrunkResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSubports") + } + b.Subports = append(b.Subports, *values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go new file mode 100644 index 000000000..c744fe03f --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.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" +) + +// TrunkSpecApplyConfiguration represents a declarative configuration of the TrunkSpec type for use +// with apply. +type TrunkSpecApplyConfiguration struct { + Import *TrunkImportApplyConfiguration `json:"import,omitempty"` + Resource *TrunkResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// TrunkSpecApplyConfiguration constructs a declarative configuration of the TrunkSpec type for use with +// apply. +func TrunkSpec() *TrunkSpecApplyConfiguration { + return &TrunkSpecApplyConfiguration{} +} + +// 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 *TrunkSpecApplyConfiguration) WithImport(value *TrunkImportApplyConfiguration) *TrunkSpecApplyConfiguration { + 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 *TrunkSpecApplyConfiguration) WithResource(value *TrunkResourceSpecApplyConfiguration) *TrunkSpecApplyConfiguration { + 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 *TrunkSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *TrunkSpecApplyConfiguration { + 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 *TrunkSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *TrunkSpecApplyConfiguration { + 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 *TrunkSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *TrunkSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go new file mode 100644 index 000000000..ffd42aeb8 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.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" +) + +// TrunkStatusApplyConfiguration represents a declarative configuration of the TrunkStatus type for use +// with apply. +type TrunkStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *TrunkResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// TrunkStatusApplyConfiguration constructs a declarative configuration of the TrunkStatus type for use with +// apply. +func TrunkStatus() *TrunkStatusApplyConfiguration { + return &TrunkStatusApplyConfiguration{} +} + +// 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 *TrunkStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *TrunkStatusApplyConfiguration { + 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 *TrunkStatusApplyConfiguration) WithID(value string) *TrunkStatusApplyConfiguration { + 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 *TrunkStatusApplyConfiguration) WithResource(value *TrunkResourceStatusApplyConfiguration) *TrunkStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunksubportspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunksubportspec.go new file mode 100644 index 000000000..16625b28b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunksubportspec.go @@ -0,0 +1,61 @@ +/* +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" +) + +// TrunkSubportSpecApplyConfiguration represents a declarative configuration of the TrunkSubportSpec type for use +// with apply. +type TrunkSubportSpecApplyConfiguration struct { + PortRef *apiv1alpha1.KubernetesNameRef `json:"portRef,omitempty"` + SegmentationID *int32 `json:"segmentationID,omitempty"` + SegmentationType *string `json:"segmentationType,omitempty"` +} + +// TrunkSubportSpecApplyConfiguration constructs a declarative configuration of the TrunkSubportSpec type for use with +// apply. +func TrunkSubportSpec() *TrunkSubportSpecApplyConfiguration { + return &TrunkSubportSpecApplyConfiguration{} +} + +// WithPortRef sets the PortRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PortRef field is set to the value of the last call. +func (b *TrunkSubportSpecApplyConfiguration) WithPortRef(value apiv1alpha1.KubernetesNameRef) *TrunkSubportSpecApplyConfiguration { + b.PortRef = &value + return b +} + +// WithSegmentationID sets the SegmentationID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SegmentationID field is set to the value of the last call. +func (b *TrunkSubportSpecApplyConfiguration) WithSegmentationID(value int32) *TrunkSubportSpecApplyConfiguration { + b.SegmentationID = &value + return b +} + +// WithSegmentationType sets the SegmentationType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SegmentationType field is set to the value of the last call. +func (b *TrunkSubportSpecApplyConfiguration) WithSegmentationType(value string) *TrunkSubportSpecApplyConfiguration { + b.SegmentationType = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunksubportstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunksubportstatus.go new file mode 100644 index 000000000..b782fa334 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunksubportstatus.go @@ -0,0 +1,57 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// TrunkSubportStatusApplyConfiguration represents a declarative configuration of the TrunkSubportStatus type for use +// with apply. +type TrunkSubportStatusApplyConfiguration struct { + PortID *string `json:"portID,omitempty"` + SegmentationID *int32 `json:"segmentationID,omitempty"` + SegmentationType *string `json:"segmentationType,omitempty"` +} + +// TrunkSubportStatusApplyConfiguration constructs a declarative configuration of the TrunkSubportStatus type for use with +// apply. +func TrunkSubportStatus() *TrunkSubportStatusApplyConfiguration { + return &TrunkSubportStatusApplyConfiguration{} +} + +// WithPortID sets the PortID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PortID field is set to the value of the last call. +func (b *TrunkSubportStatusApplyConfiguration) WithPortID(value string) *TrunkSubportStatusApplyConfiguration { + b.PortID = &value + return b +} + +// WithSegmentationID sets the SegmentationID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SegmentationID field is set to the value of the last call. +func (b *TrunkSubportStatusApplyConfiguration) WithSegmentationID(value int32) *TrunkSubportStatusApplyConfiguration { + b.SegmentationID = &value + return b +} + +// WithSegmentationType sets the SegmentationType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SegmentationType field is set to the value of the last call. +func (b *TrunkSubportStatusApplyConfiguration) WithSegmentationType(value string) *TrunkSubportStatusApplyConfiguration { + b.SegmentationType = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index dfb83d47f..09f7aa60f 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2903,6 +2903,213 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Trunk + 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.TrunkSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkFilter + map: + fields: + - name: adminStateUp + type: + scalar: boolean + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: notTags + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: notTagsAny + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: portRef + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: tags + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: tagsAny + type: + list: + elementType: + scalar: string + elementRelationship: associative +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkResourceSpec + map: + fields: + - name: adminStateUp + type: + scalar: boolean + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: portRef + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: subports + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkSubportSpec + elementRelationship: atomic + - name: tags + type: + list: + elementType: + scalar: string + elementRelationship: associative +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkResourceStatus + map: + fields: + - name: adminStateUp + type: + scalar: boolean + - name: createdAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: portID + type: + scalar: string + - name: projectID + type: + scalar: string + - name: revisionNumber + type: + scalar: numeric + - name: status + type: + scalar: string + - name: subports + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkSubportStatus + elementRelationship: atomic + - name: tags + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: tenantID + type: + scalar: string + - name: updatedAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkSpec + 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.TrunkImport + - 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.TrunkResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkStatus + 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.TrunkResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkSubportSpec + map: + fields: + - name: portRef + type: + scalar: string + - name: segmentationID + type: + scalar: numeric + - name: segmentationType + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkSubportStatus + map: + fields: + - name: portID + type: + scalar: string + - name: segmentationID + type: + scalar: numeric + - name: segmentationType + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserDataSpec map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 5a3990951..5e21a0190 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -340,6 +340,24 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.SubnetSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetStatus"): return &apiv1alpha1.SubnetStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Trunk"): + return &apiv1alpha1.TrunkApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkFilter"): + return &apiv1alpha1.TrunkFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkImport"): + return &apiv1alpha1.TrunkImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkResourceSpec"): + return &apiv1alpha1.TrunkResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkResourceStatus"): + return &apiv1alpha1.TrunkResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkSpec"): + return &apiv1alpha1.TrunkSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkStatus"): + return &apiv1alpha1.TrunkStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkSubportSpec"): + return &apiv1alpha1.TrunkSubportSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrunkSubportStatus"): + return &apiv1alpha1.TrunkSubportStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("UserDataSpec"): return &apiv1alpha1.UserDataSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Volume"): 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 4317c8aa6..91ee2fdb4 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -45,6 +45,7 @@ type OpenstackV1alpha1Interface interface { ServerGroupsGetter ServicesGetter SubnetsGetter + TrunksGetter VolumesGetter VolumeTypesGetter } @@ -122,6 +123,10 @@ func (c *OpenstackV1alpha1Client) Subnets(namespace string) SubnetInterface { return newSubnets(c, namespace) } +func (c *OpenstackV1alpha1Client) Trunks(namespace string) TrunkInterface { + return newTrunks(c, namespace) +} + func (c *OpenstackV1alpha1Client) Volumes(namespace string) VolumeInterface { return newVolumes(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 595446f05..81e95d4c9 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 @@ -96,6 +96,10 @@ func (c *FakeOpenstackV1alpha1) Subnets(namespace string) v1alpha1.SubnetInterfa return newFakeSubnets(c, namespace) } +func (c *FakeOpenstackV1alpha1) Trunks(namespace string) v1alpha1.TrunkInterface { + return newFakeTrunks(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Volumes(namespace string) v1alpha1.VolumeInterface { return newFakeVolumes(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_trunk.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_trunk.go new file mode 100644 index 000000000..bc58f0e4d --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_trunk.go @@ -0,0 +1,49 @@ +/* +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" +) + +// fakeTrunks implements TrunkInterface +type fakeTrunks struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.Trunk, *v1alpha1.TrunkList, *apiv1alpha1.TrunkApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeTrunks(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.TrunkInterface { + return &fakeTrunks{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.Trunk, *v1alpha1.TrunkList, *apiv1alpha1.TrunkApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("trunks"), + v1alpha1.SchemeGroupVersion.WithKind("Trunk"), + func() *v1alpha1.Trunk { return &v1alpha1.Trunk{} }, + func() *v1alpha1.TrunkList { return &v1alpha1.TrunkList{} }, + func(dst, src *v1alpha1.TrunkList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.TrunkList) []*v1alpha1.Trunk { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.TrunkList, items []*v1alpha1.Trunk) { 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 558e399d0..ca41372ee 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -52,6 +52,8 @@ type ServiceExpansion interface{} type SubnetExpansion interface{} +type TrunkExpansion interface{} + type VolumeExpansion interface{} type VolumeTypeExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/trunk.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/trunk.go new file mode 100644 index 000000000..0a2dd9152 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/trunk.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" +) + +// TrunksGetter has a method to return a TrunkInterface. +// A group's client should implement this interface. +type TrunksGetter interface { + Trunks(namespace string) TrunkInterface +} + +// TrunkInterface has methods to work with Trunk resources. +type TrunkInterface interface { + Create(ctx context.Context, trunk *apiv1alpha1.Trunk, opts v1.CreateOptions) (*apiv1alpha1.Trunk, error) + Update(ctx context.Context, trunk *apiv1alpha1.Trunk, opts v1.UpdateOptions) (*apiv1alpha1.Trunk, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, trunk *apiv1alpha1.Trunk, opts v1.UpdateOptions) (*apiv1alpha1.Trunk, 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.Trunk, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.TrunkList, 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.Trunk, err error) + Apply(ctx context.Context, trunk *applyconfigurationapiv1alpha1.TrunkApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Trunk, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, trunk *applyconfigurationapiv1alpha1.TrunkApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Trunk, err error) + TrunkExpansion +} + +// trunks implements TrunkInterface +type trunks struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.Trunk, *apiv1alpha1.TrunkList, *applyconfigurationapiv1alpha1.TrunkApplyConfiguration] +} + +// newTrunks returns a Trunks +func newTrunks(c *OpenstackV1alpha1Client, namespace string) *trunks { + return &trunks{ + gentype.NewClientWithListAndApply[*apiv1alpha1.Trunk, *apiv1alpha1.TrunkList, *applyconfigurationapiv1alpha1.TrunkApplyConfiguration]( + "trunks", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.Trunk { return &apiv1alpha1.Trunk{} }, + func() *apiv1alpha1.TrunkList { return &apiv1alpha1.TrunkList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 2e06781ab..28e76d19c 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -58,6 +58,8 @@ type Interface interface { Services() ServiceInformer // Subnets returns a SubnetInformer. Subnets() SubnetInformer + // Trunks returns a TrunkInformer. + Trunks() TrunkInformer // Volumes returns a VolumeInformer. Volumes() VolumeInformer // VolumeTypes returns a VolumeTypeInformer. @@ -160,6 +162,11 @@ func (v *version) Subnets() SubnetInformer { return &subnetInformer{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} +} + // Volumes returns a VolumeInformer. func (v *version) Volumes() VolumeInformer { return &volumeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/trunk.go b/pkg/clients/informers/externalversions/api/v1alpha1/trunk.go new file mode 100644 index 000000000..0f3eea4fd --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/trunk.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" +) + +// TrunkInformer provides access to a shared informer and lister for +// Trunks. +type TrunkInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.TrunkLister +} + +type trunkInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTrunkInformer constructs a new informer for Trunk 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 NewTrunkInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTrunkInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTrunkInformer constructs a new informer for Trunk 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 NewFilteredTrunkInformer(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().Trunks(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Trunks(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Trunks(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Trunks(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.Trunk{}, + resyncPeriod, + indexers, + ) +} + +func (f *trunkInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTrunkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *trunkInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.Trunk{}, f.defaultInformer) +} + +func (f *trunkInformer) Lister() apiv1alpha1.TrunkLister { + return apiv1alpha1.NewTrunkLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 1bb2313e0..f83a355c4 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -87,6 +87,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Services().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subnets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Subnets().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("trunks"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Trunks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("volumes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Volumes().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("volumetypes"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 1380d0372..722c92a5b 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -154,6 +154,14 @@ type SubnetListerExpansion interface{} // SubnetNamespaceLister. type SubnetNamespaceListerExpansion interface{} +// TrunkListerExpansion allows custom methods to be added to +// TrunkLister. +type TrunkListerExpansion interface{} + +// TrunkNamespaceListerExpansion allows custom methods to be added to +// TrunkNamespaceLister. +type TrunkNamespaceListerExpansion interface{} + // VolumeListerExpansion allows custom methods to be added to // VolumeLister. type VolumeListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/trunk.go b/pkg/clients/listers/api/v1alpha1/trunk.go new file mode 100644 index 000000000..bd1a34270 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/trunk.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" +) + +// TrunkLister helps list Trunks. +// All objects returned here must be treated as read-only. +type TrunkLister interface { + // List lists all Trunks in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Trunk, err error) + // Trunks returns an object that can list and get Trunks. + Trunks(namespace string) TrunkNamespaceLister + TrunkListerExpansion +} + +// trunkLister implements the TrunkLister interface. +type trunkLister struct { + listers.ResourceIndexer[*apiv1alpha1.Trunk] +} + +// NewTrunkLister returns a new TrunkLister. +func NewTrunkLister(indexer cache.Indexer) TrunkLister { + return &trunkLister{listers.New[*apiv1alpha1.Trunk](indexer, apiv1alpha1.Resource("trunk"))} +} + +// Trunks returns an object that can list and get Trunks. +func (s *trunkLister) Trunks(namespace string) TrunkNamespaceLister { + return trunkNamespaceLister{listers.NewNamespaced[*apiv1alpha1.Trunk](s.ResourceIndexer, namespace)} +} + +// TrunkNamespaceLister helps list and get Trunks. +// All objects returned here must be treated as read-only. +type TrunkNamespaceLister interface { + // List lists all Trunks in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Trunk, err error) + // Get retrieves the Trunk from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.Trunk, error) + TrunkNamespaceListerExpansion +} + +// trunkNamespaceLister implements the TrunkNamespaceLister +// interface. +type trunkNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.Trunk] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index d7e34fedc..5bc2c1f35 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -27,6 +27,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [ServerGroup](#servergroup) - [Service](#service) - [Subnet](#subnet) +- [Trunk](#trunk) - [Volume](#volume) - [VolumeType](#volumetype) @@ -179,6 +180,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) +- [TrunkSpec](#trunkspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -419,6 +421,7 @@ _Appears in:_ - [RouterFilter](#routerfilter) - [SecurityGroupFilter](#securitygroupfilter) - [SubnetFilter](#subnetfilter) +- [TrunkFilter](#trunkfilter) | Field | Description | Default | Validation | | --- | --- | --- | --- | @@ -1634,6 +1637,7 @@ _Appears in:_ - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) - [TrunkResourceSpec](#trunkresourcespec) +- [TrunkSubportSpec](#trunksubportspec) - [UserDataSpec](#userdataspec) - [VolumeResourceSpec](#volumeresourcespec) @@ -1694,6 +1698,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) +- [TrunkSpec](#trunkspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -1728,6 +1733,7 @@ _Appears in:_ - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) +- [TrunkSpec](#trunkspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -1920,6 +1926,8 @@ _Appears in:_ - [SecurityGroupRule](#securitygrouprule) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) +- [TrunkFilter](#trunkfilter) +- [TrunkResourceSpec](#trunkresourcespec) @@ -1937,6 +1945,7 @@ _Appears in:_ - [PortResourceStatus](#portresourcestatus) - [SecurityGroupResourceStatus](#securitygroupresourcestatus) - [SubnetResourceStatus](#subnetresourcestatus) +- [TrunkResourceStatus](#trunkresourcestatus) | Field | Description | Default | Validation | | --- | --- | --- | --- | @@ -1970,6 +1979,8 @@ _Appears in:_ - [SecurityGroupResourceSpec](#securitygroupresourcespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) +- [TrunkFilter](#trunkfilter) +- [TrunkResourceSpec](#trunkresourcespec) @@ -3825,10 +3836,193 @@ _Appears in:_ | `resource` _[SubnetResourceStatus](#subnetresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +#### Trunk + + + +Trunk is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `Trunk` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[TrunkSpec](#trunkspec)_ | spec specifies the desired state of the resource. | | | +| `status` _[TrunkStatus](#trunkstatus)_ | status defines the observed state of the resource. | | | + + +#### TrunkFilter + + + +TrunkFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [TrunkImport](#trunkimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. | | | +| `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
| +| `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
| +| `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
| +| `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
| + + +#### TrunkImport + + + +TrunkImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [TrunkSpec](#trunkspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
| +| `filter` _[TrunkFilter](#trunkfilter)_ | 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
| + + +#### TrunkResourceSpec + + + +TrunkResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [TrunkSpec](#trunkspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. If false (down),
the trunk does not forward packets. | | | +| `subports` _[TrunkSubportSpec](#trunksubportspec) array_ | subports is the list of ports to attach to the trunk. | | MaxItems: 1024
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of Neutron tags to apply to the trunk. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| + + +#### TrunkResourceStatus + + + +TrunkResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [TrunkStatus](#trunkstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| +| `portID` _string_ | portID is the ID of the Port to which the resource is associated. | | MaxLength: 1024
| +| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
| +| `tenantID` _string_ | tenantID is the project owner of the trunk (alias of projectID in some deployments). | | MaxLength: 1024
| +| `status` _string_ | status indicates whether the trunk is currently operational. | | MaxLength: 1024
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| +| `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 | | | +| `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 | | | +| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. | | | +| `subports` _[TrunkSubportStatus](#trunksubportstatus) array_ | subports is a list of ports associated with the trunk. | | MaxItems: 1024
| + + +#### TrunkSpec + + + +TrunkSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [Trunk](#trunk) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[TrunkImport](#trunkimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[TrunkResourceSpec](#trunkresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | + + +#### TrunkStatus + + + +TrunkStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [Trunk](#trunk) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `resource` _[TrunkResourceStatus](#trunkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + +#### TrunkSubportSpec + + +TrunkSubportSpec represents a subport to attach to a trunk. +It maps to gophercloud's trunks.Subport. +_Appears in:_ +- [TrunkResourceSpec](#trunkresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port that will be attached as a subport. | | MaxLength: 253
MinLength: 1
| +| `segmentationID` _integer_ | segmentationID is the segmentation ID for the subport (e.g. VLAN ID). | | Maximum: 4094
Minimum: 1
| +| `segmentationType` _string_ | segmentationType is the segmentation type for the subport (e.g. vlan). | | Enum: [inherit vlan]
MaxLength: 32
MinLength: 1
| + + +#### TrunkSubportStatus + + + +TrunkSubportStatus represents an attached subport on a trunk. +It maps to gophercloud's trunks.Subport. + + + +_Appears in:_ +- [TrunkResourceStatus](#trunkresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `portID` _string_ | portID is the OpenStack ID of the Port attached as a subport. | | MaxLength: 1024
| +| `segmentationID` _integer_ | segmentationID is the segmentation ID for the subport (e.g. VLAN ID). | | | +| `segmentationType` _string_ | segmentationType is the segmentation type for the subport (e.g. vlan). | | MaxLength: 1024
| From 6f0dc2bc0259d0393f977a6762182618c15d3471 Mon Sep 17 00:00:00 2001 From: Mohammed Al-Dokimi Date: Wed, 4 Feb 2026 12:03:14 +0100 Subject: [PATCH 034/121] Fix typo in developers docs The directory is named `controllers`, plural. --- website/docs/development/writing-tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/development/writing-tests.md b/website/docs/development/writing-tests.md index 0f8c0ccbd..0beeca6d8 100644 --- a/website/docs/development/writing-tests.md +++ b/website/docs/development/writing-tests.md @@ -36,7 +36,7 @@ can specify modules that you want to test by passing the package's path, separated by a blank space, for example: ```bash -TEST_PATHS="./internal/controller/server ./internal/controller/image" make test +TEST_PATHS="./internal/controllers/server ./internal/controllers/image" make test ``` ## E2E tests From fe20ac889951d274f3c96c8d4e1e8220b4f1e966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 5 Feb 2026 09:44:46 +0100 Subject: [PATCH 035/121] securitygroup: Fix inverted error handling for rule creation The error handling logic was inverted: retryable errors (5xx) were being marked as Terminal while non-retryable errors (4xx) would retry forever. --- internal/controllers/securitygroup/actuator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index 703f25c7c..17770b28c 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -331,7 +331,7 @@ orcRules: if len(ruleCreateOpts) > 0 { if _, createErr := actuator.osClient.CreateSecGroupRules(ctx, ruleCreateOpts); createErr != nil { // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsRetryable(createErr) { + if !orcerrors.IsRetryable(createErr) { createErr = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+createErr.Error(), createErr) } else { createErr = fmt.Errorf("creating security group rules: %w", createErr) From 828778f14607b8b1a2ed344d0d8e3d962acde08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 5 Feb 2026 10:53:57 +0100 Subject: [PATCH 036/121] server: Add availability check for Port dependencies Ensure port dependencies are checked for availability before using their IDs, consistent with the pattern established in f3a38b37a for SecurityGroup dependencies in the Port controller. --- internal/controllers/server/actuator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index acadd0be1..a044503f6 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -182,7 +182,7 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp { portsMap, portsReconcileStatus := portDependency.GetDependencies( ctx, actuator.k8sClient, obj, func(port *orcv1alpha1.Port) bool { - return port.Status.ID != nil + return orcv1alpha1.IsAvailable(port) && port.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(portsReconcileStatus) @@ -445,7 +445,7 @@ func (actuator serverActuator) reconcilePortAttachments(ctx context.Context, obj portDepsMap, reconcileStatus := portDependency.GetDependencies( ctx, actuator.k8sClient, obj, func(port *orcv1alpha1.Port) bool { - return port.Status.ID != nil + return orcv1alpha1.IsAvailable(port) && port.Status.ID != nil }, ) From ecf5a8de49075125d27986b500b08865988fc77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 22 Jan 2026 13:49:31 +0100 Subject: [PATCH 037/121] Bump KAL Also disable new failing linters. We'll look into them separately. --- .golangci.yml | 3 +++ Makefile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 3301a5c65..04711d263 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -34,6 +34,9 @@ linters: settings: linters: disable: + - arrayofstruct + - defaults + - nonpointerstructs - optionalfields enable: - commentstart diff --git a/Makefile b/Makefile index f65f038a7..d1b7e775b 100644 --- a/Makefile +++ b/Makefile @@ -315,7 +315,7 @@ KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= release-0.22 GOLANGCI_LINT_VERSION ?= v2.7.2 -KAL_VERSION ?= v0.0.0-20250924094418-502783c08f9d +KAL_VERSION ?= v0.0.0-20260205134631-d65d24a9df89 MOCKGEN_VERSION ?= v0.5.0 KUTTL_VERSION ?= v0.24.0 GOVULNCHECK_VERSION ?= v1.1.4 From 157b9b3096676ec018c636fcea414f8354674320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 23 Jan 2026 09:30:08 +0100 Subject: [PATCH 038/121] Enabling KAL linting on generated files This was a gap in our testing. Disable the failing linters, that will fix in later commits. --- .golangci.yml | 25 ++++++++++++++++++------- api/v1alpha1/image_types.go | 1 - 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 04711d263..450c08ed2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,13 +38,14 @@ linters: - defaults - nonpointerstructs - optionalfields + - maxlength + - requiredfields enable: - commentstart - conditions - duplicatemarkers - integers - jsontags - - maxlength # NOTE: we have a number of boolean fields. Should we convert them to # string? # - nobools @@ -55,7 +56,6 @@ linters: - notimestamp - nophase - optionalorrequired - - requiredfields - ssatags - statusoptional - statussubresource @@ -65,11 +65,11 @@ linters: isFirstField: Warn usePatchStrategy: Ignore useProtobuf: Forbid - requiredfields: - omitempty: - policy: Ignore + # requiredfields: + # omitempty: + # policy: Ignore exclusions: - generated: lax + generated: disable rules: - linters: - lll @@ -78,13 +78,22 @@ linters: - dupl - lll path: internal/* + - linters: + - dupl + - goimports + - unparam + path: zz_generated - linters: - kubeapilinter - path-except: api/* + path-except: ^api/* paths: - third_party$ - builtin$ - examples$ + - applyconfiguration/* + - clientset/* + - informers/* + - listers/* formatters: enable: - gofmt @@ -95,3 +104,5 @@ formatters: - third_party$ - builtin$ - examples$ +issues: + exclude-generated: disable diff --git a/api/v1alpha1/image_types.go b/api/v1alpha1/image_types.go index b05992d78..2e838a069 100644 --- a/api/v1alpha1/image_types.go +++ b/api/v1alpha1/image_types.go @@ -267,7 +267,6 @@ type ImageContent struct { // download describes how to obtain image data by downloading it from a URL. // Must be set when creating a managed image. // +required - //nolint:kubeapilinter Download *ImageContentSourceDownload `json:"download"` } From 93621ba478d629eb2faa5c20b4d1c49f3148bbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 23 Jan 2026 10:55:53 +0100 Subject: [PATCH 039/121] Add max length validation constraints for generated API Also restore the maxlength linter. --- .golangci.yml | 2 +- api/v1alpha1/zz_generated.domain-resource.go | 4 +- api/v1alpha1/zz_generated.flavor-resource.go | 4 +- .../zz_generated.floatingip-resource.go | 4 +- api/v1alpha1/zz_generated.group-resource.go | 4 +- api/v1alpha1/zz_generated.image-resource.go | 4 +- api/v1alpha1/zz_generated.keypair-resource.go | 2 + api/v1alpha1/zz_generated.network-resource.go | 4 +- api/v1alpha1/zz_generated.port-resource.go | 4 +- api/v1alpha1/zz_generated.project-resource.go | 4 +- api/v1alpha1/zz_generated.role-resource.go | 4 +- api/v1alpha1/zz_generated.router-resource.go | 4 +- .../zz_generated.securitygroup-resource.go | 4 +- api/v1alpha1/zz_generated.server-resource.go | 4 +- .../zz_generated.servergroup-resource.go | 4 +- api/v1alpha1/zz_generated.service-resource.go | 4 +- api/v1alpha1/zz_generated.subnet-resource.go | 4 +- api/v1alpha1/zz_generated.trunk-resource.go | 4 +- api/v1alpha1/zz_generated.volume-resource.go | 4 +- .../zz_generated.volumetype-resource.go | 4 +- cmd/resource-generator/data/api.template | 5 +- .../bases/openstack.k-orc.cloud_domains.yaml | 2 + .../bases/openstack.k-orc.cloud_flavors.yaml | 2 + .../openstack.k-orc.cloud_floatingips.yaml | 2 + .../bases/openstack.k-orc.cloud_groups.yaml | 2 + .../bases/openstack.k-orc.cloud_images.yaml | 2 + .../bases/openstack.k-orc.cloud_keypairs.yaml | 2 + .../bases/openstack.k-orc.cloud_networks.yaml | 2 + .../bases/openstack.k-orc.cloud_ports.yaml | 2 + .../bases/openstack.k-orc.cloud_projects.yaml | 2 + .../bases/openstack.k-orc.cloud_roles.yaml | 2 + .../bases/openstack.k-orc.cloud_routers.yaml | 2 + .../openstack.k-orc.cloud_securitygroups.yaml | 2 + .../openstack.k-orc.cloud_servergroups.yaml | 2 + .../bases/openstack.k-orc.cloud_servers.yaml | 2 + .../bases/openstack.k-orc.cloud_services.yaml | 2 + .../bases/openstack.k-orc.cloud_subnets.yaml | 2 + .../bases/openstack.k-orc.cloud_trunks.yaml | 2 + .../bases/openstack.k-orc.cloud_volumes.yaml | 2 + .../openstack.k-orc.cloud_volumetypes.yaml | 2 + website/docs/crd-reference.md | 76 +++++++++---------- 41 files changed, 137 insertions(+), 58 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 450c08ed2..1ec80c057 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,7 +38,6 @@ linters: - defaults - nonpointerstructs - optionalfields - - maxlength - requiredfields enable: - commentstart @@ -46,6 +45,7 @@ linters: - duplicatemarkers - integers - jsontags + - maxlength # NOTE: we have a number of boolean fields. Should we convert them to # string? # - nobools diff --git a/api/v1alpha1/zz_generated.domain-resource.go b/api/v1alpha1/zz_generated.domain-resource.go index 9fd2b85a4..7f72ed9eb 100644 --- a/api/v1alpha1/zz_generated.domain-resource.go +++ b/api/v1alpha1/zz_generated.domain-resource.go @@ -29,8 +29,9 @@ type DomainImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type DomainStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.flavor-resource.go b/api/v1alpha1/zz_generated.flavor-resource.go index 977264ed2..195774b20 100644 --- a/api/v1alpha1/zz_generated.flavor-resource.go +++ b/api/v1alpha1/zz_generated.flavor-resource.go @@ -29,8 +29,9 @@ type FlavorImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type FlavorStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.floatingip-resource.go b/api/v1alpha1/zz_generated.floatingip-resource.go index 66993c36e..111fb8020 100644 --- a/api/v1alpha1/zz_generated.floatingip-resource.go +++ b/api/v1alpha1/zz_generated.floatingip-resource.go @@ -29,8 +29,9 @@ type FloatingIPImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type FloatingIPStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.group-resource.go b/api/v1alpha1/zz_generated.group-resource.go index ffee5b120..377c9000f 100644 --- a/api/v1alpha1/zz_generated.group-resource.go +++ b/api/v1alpha1/zz_generated.group-resource.go @@ -29,8 +29,9 @@ type GroupImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type GroupStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.image-resource.go b/api/v1alpha1/zz_generated.image-resource.go index dc5c80d4d..72375f249 100644 --- a/api/v1alpha1/zz_generated.image-resource.go +++ b/api/v1alpha1/zz_generated.image-resource.go @@ -29,8 +29,9 @@ type ImageImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -105,6 +106,7 @@ type ImageStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.keypair-resource.go b/api/v1alpha1/zz_generated.keypair-resource.go index 77f31369a..eeab4a82e 100644 --- a/api/v1alpha1/zz_generated.keypair-resource.go +++ b/api/v1alpha1/zz_generated.keypair-resource.go @@ -30,6 +30,7 @@ type KeyPairImport struct { // the resource name as the unique identifier, not a UUID. // 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:MaxLength:=1024 // +optional ID *string `json:"id,omitempty"` @@ -104,6 +105,7 @@ type KeyPairStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.network-resource.go b/api/v1alpha1/zz_generated.network-resource.go index 6d3a89d85..887c5a64d 100644 --- a/api/v1alpha1/zz_generated.network-resource.go +++ b/api/v1alpha1/zz_generated.network-resource.go @@ -29,8 +29,9 @@ type NetworkImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type NetworkStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.port-resource.go b/api/v1alpha1/zz_generated.port-resource.go index 43f9ef7e0..231608901 100644 --- a/api/v1alpha1/zz_generated.port-resource.go +++ b/api/v1alpha1/zz_generated.port-resource.go @@ -29,8 +29,9 @@ type PortImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type PortStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.project-resource.go b/api/v1alpha1/zz_generated.project-resource.go index 0845855cb..f387d3be2 100644 --- a/api/v1alpha1/zz_generated.project-resource.go +++ b/api/v1alpha1/zz_generated.project-resource.go @@ -29,8 +29,9 @@ type ProjectImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type ProjectStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go index 20befafe8..b42bd2f0d 100644 --- a/api/v1alpha1/zz_generated.role-resource.go +++ b/api/v1alpha1/zz_generated.role-resource.go @@ -29,8 +29,9 @@ type RoleImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type RoleStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.router-resource.go b/api/v1alpha1/zz_generated.router-resource.go index bde8905b2..3389ab280 100644 --- a/api/v1alpha1/zz_generated.router-resource.go +++ b/api/v1alpha1/zz_generated.router-resource.go @@ -29,8 +29,9 @@ type RouterImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type RouterStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.securitygroup-resource.go b/api/v1alpha1/zz_generated.securitygroup-resource.go index babddf920..fe52b5a5a 100644 --- a/api/v1alpha1/zz_generated.securitygroup-resource.go +++ b/api/v1alpha1/zz_generated.securitygroup-resource.go @@ -29,8 +29,9 @@ type SecurityGroupImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type SecurityGroupStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.server-resource.go b/api/v1alpha1/zz_generated.server-resource.go index db28929e9..9b88700ae 100644 --- a/api/v1alpha1/zz_generated.server-resource.go +++ b/api/v1alpha1/zz_generated.server-resource.go @@ -29,8 +29,9 @@ type ServerImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type ServerStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.servergroup-resource.go b/api/v1alpha1/zz_generated.servergroup-resource.go index ddc30d8bb..6270474b5 100644 --- a/api/v1alpha1/zz_generated.servergroup-resource.go +++ b/api/v1alpha1/zz_generated.servergroup-resource.go @@ -29,8 +29,9 @@ type ServerGroupImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type ServerGroupStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.service-resource.go b/api/v1alpha1/zz_generated.service-resource.go index e4cbc2ecf..0c38c13e7 100644 --- a/api/v1alpha1/zz_generated.service-resource.go +++ b/api/v1alpha1/zz_generated.service-resource.go @@ -29,8 +29,9 @@ type ServiceImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type ServiceStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.subnet-resource.go b/api/v1alpha1/zz_generated.subnet-resource.go index fdff17f71..8eb1792fb 100644 --- a/api/v1alpha1/zz_generated.subnet-resource.go +++ b/api/v1alpha1/zz_generated.subnet-resource.go @@ -29,8 +29,9 @@ type SubnetImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type SubnetStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.trunk-resource.go b/api/v1alpha1/zz_generated.trunk-resource.go index 305ac1878..25df600b2 100644 --- a/api/v1alpha1/zz_generated.trunk-resource.go +++ b/api/v1alpha1/zz_generated.trunk-resource.go @@ -29,8 +29,9 @@ type TrunkImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type TrunkStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.volume-resource.go b/api/v1alpha1/zz_generated.volume-resource.go index ae830727c..b5f8a9eb1 100644 --- a/api/v1alpha1/zz_generated.volume-resource.go +++ b/api/v1alpha1/zz_generated.volume-resource.go @@ -29,8 +29,9 @@ type VolumeImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type VolumeStatus struct { 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"` diff --git a/api/v1alpha1/zz_generated.volumetype-resource.go b/api/v1alpha1/zz_generated.volumetype-resource.go index bc06ce713..8bc005c87 100644 --- a/api/v1alpha1/zz_generated.volumetype-resource.go +++ b/api/v1alpha1/zz_generated.volumetype-resource.go @@ -29,8 +29,9 @@ type VolumeTypeImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` // filter contains a resource query which is expected to return a single @@ -104,6 +105,7 @@ type VolumeTypeStatus struct { 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"` diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index 9b1655038..d96d45e92 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -30,14 +30,16 @@ type {{ .Name }}Import struct { // the resource name as the unique identifier, not a UUID. // 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:MaxLength:=1024 // +optional ID *string `json:"id,omitempty"` {{- else }} // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional ID *string `json:"id,omitempty"` {{- end }} @@ -118,6 +120,7 @@ type {{ .Name }}Status struct { 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"` diff --git a/config/crd/bases/openstack.k-orc.cloud_domains.yaml b/config/crd/bases/openstack.k-orc.cloud_domains.yaml index 893ff831c..dac80e0d0 100644 --- a/config/crd/bases/openstack.k-orc.cloud_domains.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_domains.yaml @@ -108,6 +108,7 @@ spec: 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: @@ -264,6 +265,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml index d1c930aa2..fe08618aa 100644 --- a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml @@ -120,6 +120,7 @@ spec: 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: @@ -322,6 +323,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml index 686b41c71..c622285c9 100644 --- a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml @@ -192,6 +192,7 @@ spec: 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: @@ -409,6 +410,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_groups.yaml b/config/crd/bases/openstack.k-orc.cloud_groups.yaml index e62f33c1c..b3115a0ef 100644 --- a/config/crd/bases/openstack.k-orc.cloud_groups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_groups.yaml @@ -109,6 +109,7 @@ spec: 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: @@ -269,6 +270,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_images.yaml b/config/crd/bases/openstack.k-orc.cloud_images.yaml index 6b5dce9b9..455119fca 100644 --- a/config/crd/bases/openstack.k-orc.cloud_images.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_images.yaml @@ -122,6 +122,7 @@ spec: 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: @@ -644,6 +645,7 @@ spec: type: integer 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml index 969748e90..5b3450add 100644 --- a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml @@ -104,6 +104,7 @@ spec: the resource name as the unique identifier, not a UUID. 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. + maxLength: 1024 type: string type: object managedOptions: @@ -265,6 +266,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_networks.yaml b/config/crd/bases/openstack.k-orc.cloud_networks.yaml index bba47e4c8..5a1ba8a11 100644 --- a/config/crd/bases/openstack.k-orc.cloud_networks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_networks.yaml @@ -178,6 +178,7 @@ spec: 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: @@ -406,6 +407,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index a533fefb8..c67b8204f 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -192,6 +192,7 @@ spec: 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: @@ -515,6 +516,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_projects.yaml b/config/crd/bases/openstack.k-orc.cloud_projects.yaml index ab550af2b..077e98400 100644 --- a/config/crd/bases/openstack.k-orc.cloud_projects.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_projects.yaml @@ -148,6 +148,7 @@ spec: 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: @@ -314,6 +315,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_roles.yaml b/config/crd/bases/openstack.k-orc.cloud_roles.yaml index 98cb4993d..46e304512 100644 --- a/config/crd/bases/openstack.k-orc.cloud_roles.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_roles.yaml @@ -109,6 +109,7 @@ spec: 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: @@ -269,6 +270,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_routers.yaml b/config/crd/bases/openstack.k-orc.cloud_routers.yaml index 870aee9db..df796a484 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routers.yaml @@ -173,6 +173,7 @@ spec: 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: @@ -396,6 +397,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml index 13cef5e35..8f7d9ee04 100644 --- a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml @@ -173,6 +173,7 @@ spec: 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: @@ -471,6 +472,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml index ad2eafd83..941ef12fd 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml @@ -104,6 +104,7 @@ spec: 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: @@ -277,6 +278,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index 2a882bad3..0f926de7b 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -154,6 +154,7 @@ spec: 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: @@ -454,6 +455,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_services.yaml b/config/crd/bases/openstack.k-orc.cloud_services.yaml index 9e6a16416..6c04ef466 100644 --- a/config/crd/bases/openstack.k-orc.cloud_services.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_services.yaml @@ -109,6 +109,7 @@ spec: 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: @@ -273,6 +274,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml index e0445ee80..9a493d18e 100644 --- a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml @@ -220,6 +220,7 @@ spec: 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: @@ -562,6 +563,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml index a8c855f63..4dcfc5f1c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml @@ -182,6 +182,7 @@ spec: 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: @@ -408,6 +409,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml index eeaf10a8b..0b5ef7358 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml @@ -119,6 +119,7 @@ spec: 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: @@ -334,6 +335,7 @@ spec: 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 diff --git a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml index c92df01fc..a785ea23a 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml @@ -113,6 +113,7 @@ spec: 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: @@ -288,6 +289,7 @@ spec: 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 diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index fa6174a28..a62fbb623 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -259,7 +259,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[DomainFilter](#domainfilter)_ | 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
| @@ -333,7 +333,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[DomainResourceStatus](#domainresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -522,7 +522,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[FlavorFilter](#flavorfilter)_ | 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
| @@ -606,7 +606,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[FlavorResourceStatus](#flavorresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -671,7 +671,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[FloatingIPFilter](#floatingipfilter)_ | 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
| @@ -760,7 +760,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[FloatingIPResourceStatus](#floatingipresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -817,7 +817,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[GroupFilter](#groupfilter)_ | 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
| @@ -891,7 +891,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -1248,7 +1248,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[ImageFilter](#imagefilter)_ | 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
| @@ -1394,7 +1394,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[ImageResourceStatus](#imageresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | | `downloadAttempts` _integer_ | downloadAttempts is the number of times the controller has attempted to download the image contents | | | @@ -1504,7 +1504,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `id` _string_ | id contains the name of an existing resource. Note: This resource uses
the resource name as the unique identifier, not a UUID.
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. | | | +| `id` _string_ | id contains the name of an existing resource. Note: This resource uses
the resource name as the unique identifier, not a UUID.
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. | | MaxLength: 1024
| | `filter` _[KeyPairFilter](#keypairfilter)_ | 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
| @@ -1579,7 +1579,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[KeyPairResourceStatus](#keypairresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -1823,7 +1823,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[NetworkFilter](#networkfilter)_ | 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
| @@ -1919,7 +1919,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[NetworkResourceStatus](#networkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -2129,7 +2129,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[PortFilter](#portfilter)_ | 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
| @@ -2298,7 +2298,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[PortResourceStatus](#portresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -2358,7 +2358,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[ProjectFilter](#projectfilter)_ | 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
| @@ -2434,7 +2434,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[ProjectResourceStatus](#projectresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -2548,7 +2548,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[RoleFilter](#rolefilter)_ | 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
| @@ -2622,7 +2622,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[RoleResourceStatus](#roleresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -2684,7 +2684,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[RouterFilter](#routerfilter)_ | 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
| @@ -2841,7 +2841,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[RouterResourceStatus](#routerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -2917,7 +2917,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[SecurityGroupFilter](#securitygroupfilter)_ | 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
| @@ -3045,7 +3045,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[SecurityGroupResourceStatus](#securitygroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -3142,7 +3142,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[ServerGroupFilter](#servergroupfilter)_ | 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
| @@ -3270,7 +3270,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[ServerGroupResourceStatus](#servergroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -3290,7 +3290,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[ServerFilter](#serverfilter)_ | 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
| @@ -3470,7 +3470,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[ServerResourceStatus](#serverresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -3578,7 +3578,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[ServiceFilter](#servicefilter)_ | 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
| @@ -3654,7 +3654,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[ServiceResourceStatus](#serviceresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -3751,7 +3751,7 @@ _Appears in:_ | 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
| +| `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
| | `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
| @@ -3853,7 +3853,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[SubnetResourceStatus](#subnetresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -3917,7 +3917,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[TrunkFilter](#trunkfilter)_ | 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
| @@ -4004,7 +4004,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[TrunkResourceStatus](#trunkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -4140,7 +4140,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[VolumeFilter](#volumefilter)_ | 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
| @@ -4271,7 +4271,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[VolumeResourceStatus](#volumeresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | @@ -4363,7 +4363,7 @@ _Appears in:_ | 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
| +| `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
| | `filter` _[VolumeTypeFilter](#volumetypefilter)_ | 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
| @@ -4439,7 +4439,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | | +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| | `resource` _[VolumeTypeResourceStatus](#volumetyperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | From d801092a26ae66f6d461accf7d05e7a0d8ce5644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 23 Jan 2026 14:03:47 +0100 Subject: [PATCH 040/121] Restore the requiredfields KAL linter Fix the missing `omitzero` marker for CloudCredentialsRef. According to the kubernetes API conventions [1]: > Required structs should use omitzero to avoid marshalling the zero value. Also add a missing `omitempty` for the image Download. I left it as a pointer due to reasons explained here [2]. [1] https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#optional-vs-required [2] https://github.com/k-orc/openstack-resource-controller/pull/270#discussion_r1967983230 --- .golangci.yml | 8 ++++---- api/v1alpha1/image_types.go | 2 +- api/v1alpha1/zz_generated.domain-resource.go | 2 +- api/v1alpha1/zz_generated.flavor-resource.go | 2 +- api/v1alpha1/zz_generated.floatingip-resource.go | 2 +- api/v1alpha1/zz_generated.group-resource.go | 2 +- api/v1alpha1/zz_generated.image-resource.go | 2 +- api/v1alpha1/zz_generated.keypair-resource.go | 2 +- api/v1alpha1/zz_generated.network-resource.go | 2 +- api/v1alpha1/zz_generated.port-resource.go | 2 +- api/v1alpha1/zz_generated.project-resource.go | 2 +- api/v1alpha1/zz_generated.role-resource.go | 2 +- api/v1alpha1/zz_generated.router-resource.go | 2 +- api/v1alpha1/zz_generated.securitygroup-resource.go | 2 +- api/v1alpha1/zz_generated.server-resource.go | 2 +- api/v1alpha1/zz_generated.servergroup-resource.go | 2 +- api/v1alpha1/zz_generated.service-resource.go | 2 +- api/v1alpha1/zz_generated.subnet-resource.go | 2 +- api/v1alpha1/zz_generated.trunk-resource.go | 2 +- api/v1alpha1/zz_generated.volume-resource.go | 2 +- api/v1alpha1/zz_generated.volumetype-resource.go | 2 +- cmd/resource-generator/data/api.template | 2 +- 22 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1ec80c057..1ffbc1a6e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,7 +38,6 @@ linters: - defaults - nonpointerstructs - optionalfields - - requiredfields enable: - commentstart - conditions @@ -56,6 +55,7 @@ linters: - notimestamp - nophase - optionalorrequired + - requiredfields - ssatags - statusoptional - statussubresource @@ -65,9 +65,9 @@ linters: isFirstField: Warn usePatchStrategy: Ignore useProtobuf: Forbid - # requiredfields: - # omitempty: - # policy: Ignore + requiredfields: + omitempty: + policy: Ignore exclusions: generated: disable rules: diff --git a/api/v1alpha1/image_types.go b/api/v1alpha1/image_types.go index 2e838a069..de6014973 100644 --- a/api/v1alpha1/image_types.go +++ b/api/v1alpha1/image_types.go @@ -267,7 +267,7 @@ type ImageContent struct { // download describes how to obtain image data by downloading it from a URL. // Must be set when creating a managed image. // +required - Download *ImageContentSourceDownload `json:"download"` + Download *ImageContentSourceDownload `json:"download,omitempty"` } type ImageContentSourceDownload struct { diff --git a/api/v1alpha1/zz_generated.domain-resource.go b/api/v1alpha1/zz_generated.domain-resource.go index 7f72ed9eb..72f187694 100644 --- a/api/v1alpha1/zz_generated.domain-resource.go +++ b/api/v1alpha1/zz_generated.domain-resource.go @@ -77,7 +77,7 @@ type DomainSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // DomainStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.flavor-resource.go b/api/v1alpha1/zz_generated.flavor-resource.go index 195774b20..7ec4b4e9e 100644 --- a/api/v1alpha1/zz_generated.flavor-resource.go +++ b/api/v1alpha1/zz_generated.flavor-resource.go @@ -77,7 +77,7 @@ type FlavorSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // FlavorStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.floatingip-resource.go b/api/v1alpha1/zz_generated.floatingip-resource.go index 111fb8020..3f522fd30 100644 --- a/api/v1alpha1/zz_generated.floatingip-resource.go +++ b/api/v1alpha1/zz_generated.floatingip-resource.go @@ -77,7 +77,7 @@ type FloatingIPSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // FloatingIPStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.group-resource.go b/api/v1alpha1/zz_generated.group-resource.go index 377c9000f..5ee5ff870 100644 --- a/api/v1alpha1/zz_generated.group-resource.go +++ b/api/v1alpha1/zz_generated.group-resource.go @@ -77,7 +77,7 @@ type GroupSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // GroupStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.image-resource.go b/api/v1alpha1/zz_generated.image-resource.go index 72375f249..3c34b51fc 100644 --- a/api/v1alpha1/zz_generated.image-resource.go +++ b/api/v1alpha1/zz_generated.image-resource.go @@ -78,7 +78,7 @@ type ImageSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // ImageStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.keypair-resource.go b/api/v1alpha1/zz_generated.keypair-resource.go index eeab4a82e..51396f94c 100644 --- a/api/v1alpha1/zz_generated.keypair-resource.go +++ b/api/v1alpha1/zz_generated.keypair-resource.go @@ -77,7 +77,7 @@ type KeyPairSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // KeyPairStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.network-resource.go b/api/v1alpha1/zz_generated.network-resource.go index 887c5a64d..d07404fbf 100644 --- a/api/v1alpha1/zz_generated.network-resource.go +++ b/api/v1alpha1/zz_generated.network-resource.go @@ -77,7 +77,7 @@ type NetworkSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // NetworkStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.port-resource.go b/api/v1alpha1/zz_generated.port-resource.go index 231608901..9ca7246e6 100644 --- a/api/v1alpha1/zz_generated.port-resource.go +++ b/api/v1alpha1/zz_generated.port-resource.go @@ -77,7 +77,7 @@ type PortSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // PortStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.project-resource.go b/api/v1alpha1/zz_generated.project-resource.go index f387d3be2..5d8de6c87 100644 --- a/api/v1alpha1/zz_generated.project-resource.go +++ b/api/v1alpha1/zz_generated.project-resource.go @@ -77,7 +77,7 @@ type ProjectSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // ProjectStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go index b42bd2f0d..a2b93d535 100644 --- a/api/v1alpha1/zz_generated.role-resource.go +++ b/api/v1alpha1/zz_generated.role-resource.go @@ -77,7 +77,7 @@ type RoleSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // RoleStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.router-resource.go b/api/v1alpha1/zz_generated.router-resource.go index 3389ab280..6ab6f8ab5 100644 --- a/api/v1alpha1/zz_generated.router-resource.go +++ b/api/v1alpha1/zz_generated.router-resource.go @@ -77,7 +77,7 @@ type RouterSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // RouterStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.securitygroup-resource.go b/api/v1alpha1/zz_generated.securitygroup-resource.go index fe52b5a5a..80f032ee5 100644 --- a/api/v1alpha1/zz_generated.securitygroup-resource.go +++ b/api/v1alpha1/zz_generated.securitygroup-resource.go @@ -77,7 +77,7 @@ type SecurityGroupSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // SecurityGroupStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.server-resource.go b/api/v1alpha1/zz_generated.server-resource.go index 9b88700ae..726574d23 100644 --- a/api/v1alpha1/zz_generated.server-resource.go +++ b/api/v1alpha1/zz_generated.server-resource.go @@ -77,7 +77,7 @@ type ServerSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // ServerStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.servergroup-resource.go b/api/v1alpha1/zz_generated.servergroup-resource.go index 6270474b5..37bc4269b 100644 --- a/api/v1alpha1/zz_generated.servergroup-resource.go +++ b/api/v1alpha1/zz_generated.servergroup-resource.go @@ -77,7 +77,7 @@ type ServerGroupSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // ServerGroupStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.service-resource.go b/api/v1alpha1/zz_generated.service-resource.go index 0c38c13e7..983e70a68 100644 --- a/api/v1alpha1/zz_generated.service-resource.go +++ b/api/v1alpha1/zz_generated.service-resource.go @@ -77,7 +77,7 @@ type ServiceSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // ServiceStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.subnet-resource.go b/api/v1alpha1/zz_generated.subnet-resource.go index 8eb1792fb..6e933ace3 100644 --- a/api/v1alpha1/zz_generated.subnet-resource.go +++ b/api/v1alpha1/zz_generated.subnet-resource.go @@ -77,7 +77,7 @@ type SubnetSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // SubnetStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.trunk-resource.go b/api/v1alpha1/zz_generated.trunk-resource.go index 25df600b2..df755096b 100644 --- a/api/v1alpha1/zz_generated.trunk-resource.go +++ b/api/v1alpha1/zz_generated.trunk-resource.go @@ -77,7 +77,7 @@ type TrunkSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // TrunkStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.volume-resource.go b/api/v1alpha1/zz_generated.volume-resource.go index b5f8a9eb1..a83635835 100644 --- a/api/v1alpha1/zz_generated.volume-resource.go +++ b/api/v1alpha1/zz_generated.volume-resource.go @@ -77,7 +77,7 @@ type VolumeSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // VolumeStatus defines the observed state of an ORC resource. diff --git a/api/v1alpha1/zz_generated.volumetype-resource.go b/api/v1alpha1/zz_generated.volumetype-resource.go index 8bc005c87..ef1c3b4ad 100644 --- a/api/v1alpha1/zz_generated.volumetype-resource.go +++ b/api/v1alpha1/zz_generated.volumetype-resource.go @@ -77,7 +77,7 @@ type VolumeTypeSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // VolumeTypeStatus defines the observed state of an ORC resource. diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index d96d45e92..4299f52b8 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -92,7 +92,7 @@ type {{ .Name }}Spec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // {{ .Name }}Status defines the observed state of an ORC resource. From 490982fe4f610d552c530fe721ab744a1b479976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 22 Jan 2026 09:55:46 +0100 Subject: [PATCH 041/121] api: explicitly set default marker to +kubebuilder:default kube-api-linter prefers using the `+default` marker and will warn about our use of `+kubebuilder:default`. We could switch the API to use `+default`, however `elastic/crd-ref-docs` doesn't yet understand this marker. A patch merged to add support but it's not yet in a release. Let's stick to `+kubebuilder:default` for now. --- .golangci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 1ffbc1a6e..7d9025dba 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,12 +35,12 @@ linters: linters: disable: - arrayofstruct - - defaults - nonpointerstructs - optionalfields enable: - commentstart - conditions + - defaults - duplicatemarkers - integers - jsontags @@ -65,6 +65,9 @@ linters: isFirstField: Warn usePatchStrategy: Ignore useProtobuf: Forbid + defaults: + # Let's use `+kubebuilder:default` until elastic/crd-ref-docs supports `+default` + preferredDefaultMarker: "kubebuilder:default" requiredfields: omitempty: policy: Ignore From 44ba3619bab1e6b6215f2d5545ef3b3082d82767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 22 Jan 2026 10:03:04 +0100 Subject: [PATCH 042/121] ci: disable new arrayofstruct linter for KAL The `arrayofstruct` linter flags the struct we're using for the Statuses, were we report _exactly_ what OpenStack returns and don't do validation on purpose. From the things this new linter warns about, there's only one struct that is not a struct used in Status, however this struct has validation to ensure it has at least one element. More context on the new check at https://github.com/kubernetes-sigs/kube-api-linter/blob/main/docs/linters.md#arrayofstruct --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 7d9025dba..48c5c48fa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -34,7 +34,9 @@ linters: settings: linters: disable: + # NOTE: conflicts with the lack of validation in the Status structs - arrayofstruct + # NOTE: The following checks are currently failing - nonpointerstructs - optionalfields enable: From 4ca9cfdb69f3ff6b5d00929c110650e44575ce97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 22 Jan 2026 10:05:16 +0100 Subject: [PATCH 043/121] api: mark structs with required fields as required Non-pointer structs with required fields must be marked as required themselves. Also use omitzero instead of omitempty to properly handle the zero value for the struct. --- .golangci.yml | 2 +- api/v1alpha1/router_interface_types.go | 4 ++-- api/v1alpha1/zz_generated.domain-resource.go | 4 ++-- api/v1alpha1/zz_generated.flavor-resource.go | 4 ++-- .../zz_generated.floatingip-resource.go | 4 ++-- api/v1alpha1/zz_generated.group-resource.go | 4 ++-- api/v1alpha1/zz_generated.image-resource.go | 4 ++-- api/v1alpha1/zz_generated.keypair-resource.go | 4 ++-- api/v1alpha1/zz_generated.network-resource.go | 4 ++-- api/v1alpha1/zz_generated.port-resource.go | 4 ++-- api/v1alpha1/zz_generated.project-resource.go | 4 ++-- api/v1alpha1/zz_generated.role-resource.go | 4 ++-- api/v1alpha1/zz_generated.router-resource.go | 4 ++-- .../zz_generated.securitygroup-resource.go | 4 ++-- api/v1alpha1/zz_generated.server-resource.go | 4 ++-- .../zz_generated.servergroup-resource.go | 4 ++-- api/v1alpha1/zz_generated.service-resource.go | 4 ++-- api/v1alpha1/zz_generated.subnet-resource.go | 4 ++-- api/v1alpha1/zz_generated.trunk-resource.go | 4 ++-- api/v1alpha1/zz_generated.volume-resource.go | 4 ++-- .../zz_generated.volumetype-resource.go | 4 ++-- cmd/models-schema/zz_generated.openapi.go | 20 +++++++++++++++++++ cmd/resource-generator/data/api.template | 4 ++-- .../bases/openstack.k-orc.cloud_domains.yaml | 2 ++ .../bases/openstack.k-orc.cloud_flavors.yaml | 2 ++ .../openstack.k-orc.cloud_floatingips.yaml | 2 ++ .../bases/openstack.k-orc.cloud_groups.yaml | 2 ++ .../bases/openstack.k-orc.cloud_images.yaml | 2 ++ .../bases/openstack.k-orc.cloud_keypairs.yaml | 2 ++ .../bases/openstack.k-orc.cloud_networks.yaml | 2 ++ .../bases/openstack.k-orc.cloud_ports.yaml | 2 ++ .../bases/openstack.k-orc.cloud_projects.yaml | 2 ++ .../bases/openstack.k-orc.cloud_roles.yaml | 2 ++ ...penstack.k-orc.cloud_routerinterfaces.yaml | 2 ++ .../bases/openstack.k-orc.cloud_routers.yaml | 2 ++ .../openstack.k-orc.cloud_securitygroups.yaml | 2 ++ .../openstack.k-orc.cloud_servergroups.yaml | 2 ++ .../bases/openstack.k-orc.cloud_servers.yaml | 2 ++ .../bases/openstack.k-orc.cloud_services.yaml | 2 ++ .../bases/openstack.k-orc.cloud_subnets.yaml | 2 ++ .../bases/openstack.k-orc.cloud_trunks.yaml | 2 ++ .../bases/openstack.k-orc.cloud_volumes.yaml | 2 ++ .../openstack.k-orc.cloud_volumetypes.yaml | 2 ++ 43 files changed, 103 insertions(+), 43 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 48c5c48fa..ac59808d0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -37,7 +37,6 @@ linters: # NOTE: conflicts with the lack of validation in the Status structs - arrayofstruct # NOTE: The following checks are currently failing - - nonpointerstructs - optionalfields enable: - commentstart @@ -53,6 +52,7 @@ linters: - nodurations - nofloats - nomaps + - nonpointerstructs - nonullable - notimestamp - nophase diff --git a/api/v1alpha1/router_interface_types.go b/api/v1alpha1/router_interface_types.go index 2676d07bd..236c9bebf 100644 --- a/api/v1alpha1/router_interface_types.go +++ b/api/v1alpha1/router_interface_types.go @@ -36,8 +36,8 @@ type RouterInterface struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec RouterInterfaceSpec `json:"spec,omitempty"` + // +required + Spec RouterInterfaceSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.domain-resource.go b/api/v1alpha1/zz_generated.domain-resource.go index 72f187694..07505e07b 100644 --- a/api/v1alpha1/zz_generated.domain-resource.go +++ b/api/v1alpha1/zz_generated.domain-resource.go @@ -137,8 +137,8 @@ type Domain struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec DomainSpec `json:"spec,omitempty"` + // +required + Spec DomainSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.flavor-resource.go b/api/v1alpha1/zz_generated.flavor-resource.go index 7ec4b4e9e..c3ca4a8b6 100644 --- a/api/v1alpha1/zz_generated.flavor-resource.go +++ b/api/v1alpha1/zz_generated.flavor-resource.go @@ -137,8 +137,8 @@ type Flavor struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec FlavorSpec `json:"spec,omitempty"` + // +required + Spec FlavorSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.floatingip-resource.go b/api/v1alpha1/zz_generated.floatingip-resource.go index 3f522fd30..6d7501526 100644 --- a/api/v1alpha1/zz_generated.floatingip-resource.go +++ b/api/v1alpha1/zz_generated.floatingip-resource.go @@ -138,8 +138,8 @@ type FloatingIP struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec FloatingIPSpec `json:"spec,omitempty"` + // +required + Spec FloatingIPSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.group-resource.go b/api/v1alpha1/zz_generated.group-resource.go index 5ee5ff870..51e19eedd 100644 --- a/api/v1alpha1/zz_generated.group-resource.go +++ b/api/v1alpha1/zz_generated.group-resource.go @@ -137,8 +137,8 @@ type Group struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec GroupSpec `json:"spec,omitempty"` + // +required + Spec GroupSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.image-resource.go b/api/v1alpha1/zz_generated.image-resource.go index 3c34b51fc..a74b4cd38 100644 --- a/api/v1alpha1/zz_generated.image-resource.go +++ b/api/v1alpha1/zz_generated.image-resource.go @@ -140,8 +140,8 @@ type Image struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec ImageSpec `json:"spec,omitempty"` + // +required + Spec ImageSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.keypair-resource.go b/api/v1alpha1/zz_generated.keypair-resource.go index 51396f94c..4703a9f17 100644 --- a/api/v1alpha1/zz_generated.keypair-resource.go +++ b/api/v1alpha1/zz_generated.keypair-resource.go @@ -137,8 +137,8 @@ type KeyPair struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec KeyPairSpec `json:"spec,omitempty"` + // +required + Spec KeyPairSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.network-resource.go b/api/v1alpha1/zz_generated.network-resource.go index d07404fbf..5e0248b92 100644 --- a/api/v1alpha1/zz_generated.network-resource.go +++ b/api/v1alpha1/zz_generated.network-resource.go @@ -137,8 +137,8 @@ type Network struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec NetworkSpec `json:"spec,omitempty"` + // +required + Spec NetworkSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.port-resource.go b/api/v1alpha1/zz_generated.port-resource.go index 9ca7246e6..5f707e564 100644 --- a/api/v1alpha1/zz_generated.port-resource.go +++ b/api/v1alpha1/zz_generated.port-resource.go @@ -138,8 +138,8 @@ type Port struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec PortSpec `json:"spec,omitempty"` + // +required + Spec PortSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.project-resource.go b/api/v1alpha1/zz_generated.project-resource.go index 5d8de6c87..473442d35 100644 --- a/api/v1alpha1/zz_generated.project-resource.go +++ b/api/v1alpha1/zz_generated.project-resource.go @@ -137,8 +137,8 @@ type Project struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec ProjectSpec `json:"spec,omitempty"` + // +required + Spec ProjectSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go index a2b93d535..31a915d25 100644 --- a/api/v1alpha1/zz_generated.role-resource.go +++ b/api/v1alpha1/zz_generated.role-resource.go @@ -137,8 +137,8 @@ type Role struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec RoleSpec `json:"spec,omitempty"` + // +required + Spec RoleSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.router-resource.go b/api/v1alpha1/zz_generated.router-resource.go index 6ab6f8ab5..83b0681de 100644 --- a/api/v1alpha1/zz_generated.router-resource.go +++ b/api/v1alpha1/zz_generated.router-resource.go @@ -137,8 +137,8 @@ type Router struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec RouterSpec `json:"spec,omitempty"` + // +required + Spec RouterSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.securitygroup-resource.go b/api/v1alpha1/zz_generated.securitygroup-resource.go index 80f032ee5..f378c1b29 100644 --- a/api/v1alpha1/zz_generated.securitygroup-resource.go +++ b/api/v1alpha1/zz_generated.securitygroup-resource.go @@ -137,8 +137,8 @@ type SecurityGroup struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec SecurityGroupSpec `json:"spec,omitempty"` + // +required + Spec SecurityGroupSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.server-resource.go b/api/v1alpha1/zz_generated.server-resource.go index 726574d23..d00f9f934 100644 --- a/api/v1alpha1/zz_generated.server-resource.go +++ b/api/v1alpha1/zz_generated.server-resource.go @@ -137,8 +137,8 @@ type Server struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec ServerSpec `json:"spec,omitempty"` + // +required + Spec ServerSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.servergroup-resource.go b/api/v1alpha1/zz_generated.servergroup-resource.go index 37bc4269b..88f875511 100644 --- a/api/v1alpha1/zz_generated.servergroup-resource.go +++ b/api/v1alpha1/zz_generated.servergroup-resource.go @@ -137,8 +137,8 @@ type ServerGroup struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec ServerGroupSpec `json:"spec,omitempty"` + // +required + Spec ServerGroupSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.service-resource.go b/api/v1alpha1/zz_generated.service-resource.go index 983e70a68..1b9275360 100644 --- a/api/v1alpha1/zz_generated.service-resource.go +++ b/api/v1alpha1/zz_generated.service-resource.go @@ -137,8 +137,8 @@ type Service struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec ServiceSpec `json:"spec,omitempty"` + // +required + Spec ServiceSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.subnet-resource.go b/api/v1alpha1/zz_generated.subnet-resource.go index 6e933ace3..c6fcc4090 100644 --- a/api/v1alpha1/zz_generated.subnet-resource.go +++ b/api/v1alpha1/zz_generated.subnet-resource.go @@ -137,8 +137,8 @@ type Subnet struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec SubnetSpec `json:"spec,omitempty"` + // +required + Spec SubnetSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.trunk-resource.go b/api/v1alpha1/zz_generated.trunk-resource.go index df755096b..00aefdd24 100644 --- a/api/v1alpha1/zz_generated.trunk-resource.go +++ b/api/v1alpha1/zz_generated.trunk-resource.go @@ -137,8 +137,8 @@ type Trunk struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec TrunkSpec `json:"spec,omitempty"` + // +required + Spec TrunkSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.volume-resource.go b/api/v1alpha1/zz_generated.volume-resource.go index a83635835..839df23da 100644 --- a/api/v1alpha1/zz_generated.volume-resource.go +++ b/api/v1alpha1/zz_generated.volume-resource.go @@ -137,8 +137,8 @@ type Volume struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec VolumeSpec `json:"spec,omitempty"` + // +required + Spec VolumeSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/api/v1alpha1/zz_generated.volumetype-resource.go b/api/v1alpha1/zz_generated.volumetype-resource.go index ef1c3b4ad..4ae6b2a85 100644 --- a/api/v1alpha1/zz_generated.volumetype-resource.go +++ b/api/v1alpha1/zz_generated.volumetype-resource.go @@ -137,8 +137,8 @@ type VolumeType struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec VolumeTypeSpec `json:"spec,omitempty"` + // +required + Spec VolumeTypeSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index cc7b895d2..285618a39 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -730,6 +730,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref common.Refe }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -1395,6 +1396,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref common.Refe }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -1805,6 +1807,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIP(ref common. }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -2367,6 +2370,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Group(ref common.Refer }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -2797,6 +2801,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Image(ref common.Refer }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -3517,6 +3522,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPair(ref common.Ref }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -3861,6 +3867,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref common.Ref }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -4530,6 +4537,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref common.Refere }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -5309,6 +5317,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref common.Ref }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -5779,6 +5788,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref common.Refere }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -6103,6 +6113,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Router(ref common.Refe }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -6295,6 +6306,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterface(ref co }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -6842,6 +6854,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroup(ref comm }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -7494,6 +7507,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref common.Refe }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -7651,6 +7665,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref common }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -8711,6 +8726,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Service(ref common.Ref }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -9050,6 +9066,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref common.Refe }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -9821,6 +9838,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Trunk(ref common.Refer }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -10478,6 +10496,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref common.Refe }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ @@ -11115,6 +11134,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeType(ref common. }, }, }, + Required: []string{"spec"}, }, }, Dependencies: []string{ diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index 4299f52b8..ecdc787b2 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -159,8 +159,8 @@ type {{ .Name }} struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec {{ .Name }}Spec `json:"spec,omitempty"` + // +required + Spec {{ .Name }}Spec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/config/crd/bases/openstack.k-orc.cloud_domains.yaml b/config/crd/bases/openstack.k-orc.cloud_domains.yaml index dac80e0d0..a1870a5a5 100644 --- a/config/crd/bases/openstack.k-orc.cloud_domains.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_domains.yaml @@ -288,6 +288,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml index fe08618aa..833f1ea47 100644 --- a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml @@ -368,6 +368,8 @@ spec: type: integer type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml index c622285c9..383366e59 100644 --- a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml @@ -481,6 +481,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_groups.yaml b/config/crd/bases/openstack.k-orc.cloud_groups.yaml index b3115a0ef..d29e2161a 100644 --- a/config/crd/bases/openstack.k-orc.cloud_groups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_groups.yaml @@ -293,6 +293,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_images.yaml b/config/crd/bases/openstack.k-orc.cloud_images.yaml index 455119fca..ff0f543a8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_images.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_images.yaml @@ -715,6 +715,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml index 5b3450add..df1f02cab 100644 --- a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml @@ -291,6 +291,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_networks.yaml b/config/crd/bases/openstack.k-orc.cloud_networks.yaml index 5a1ba8a11..63951bb0b 100644 --- a/config/crd/bases/openstack.k-orc.cloud_networks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_networks.yaml @@ -542,6 +542,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index c67b8204f..d4e4999b7 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -656,6 +656,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_projects.yaml b/config/crd/bases/openstack.k-orc.cloud_projects.yaml index 077e98400..6c6804d01 100644 --- a/config/crd/bases/openstack.k-orc.cloud_projects.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_projects.yaml @@ -345,6 +345,8 @@ spec: x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_roles.yaml b/config/crd/bases/openstack.k-orc.cloud_roles.yaml index 46e304512..2635b7063 100644 --- a/config/crd/bases/openstack.k-orc.cloud_roles.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_roles.yaml @@ -293,6 +293,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml b/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml index 83075e0c7..2770876c0 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml @@ -162,6 +162,8 @@ spec: maxLength: 1024 type: string type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_routers.yaml b/config/crd/bases/openstack.k-orc.cloud_routers.yaml index df796a484..dea6ac3a8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routers.yaml @@ -460,6 +460,8 @@ spec: x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml index 8f7d9ee04..bec05f320 100644 --- a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml @@ -590,6 +590,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml index 941ef12fd..c44d85f12 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml @@ -313,6 +313,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index 0f926de7b..8387dd81c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -590,6 +590,8 @@ spec: x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_services.yaml b/config/crd/bases/openstack.k-orc.cloud_services.yaml index 6c04ef466..0c113a367 100644 --- a/config/crd/bases/openstack.k-orc.cloud_services.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_services.yaml @@ -299,6 +299,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml index 9a493d18e..a37539475 100644 --- a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml @@ -697,6 +697,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml index 4dcfc5f1c..536db8166 100644 --- a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml @@ -497,6 +497,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml index 0b5ef7358..6a9371f5c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml @@ -474,6 +474,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml index a785ea23a..20f821828 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml @@ -327,6 +327,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true From 69a16e61baf81d8c86e81d63f90bbc35bd882cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 6 Feb 2026 14:03:42 +0100 Subject: [PATCH 044/121] port: make hostID immutable When a port with hostID set is attached to a server, OpenStack rebinds the port to the actual compute host, creating drift between the ORC spec and reality. The controller would then fight by trying to push the spec value back. Make hostID immutable via CEL validation to prevent this inconsistency. Remove the now-dead hostID update path from the controller and the port-update-admin e2e test that exercised it. Fixes #659. --- api/v1alpha1/port_types.go | 1 + .../bases/openstack.k-orc.cloud_ports.yaml | 2 ++ internal/controllers/port/actuator.go | 21 ++----------- internal/controllers/port/actuator_test.go | 2 +- .../port/tests/port-update/00-assert.yaml | 30 ------------------- .../port-update/00-minimal-resource.yaml | 14 --------- .../port/tests/port-update/01-assert.yaml | 15 ---------- .../port-update/01-updated-resource.yaml | 13 -------- test/apivalidations/port_test.go | 14 +++++++++ 9 files changed, 20 insertions(+), 92 deletions(-) diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index d54f3cf95..9fe568189 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -203,6 +203,7 @@ type PortResourceSpec struct { // hostID specifies the host where the port will be bound. // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="hostID is immutable" HostID *HostID `json:"hostID,omitempty"` } diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index d4e4999b7..9bcb20f0b 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -322,6 +322,8 @@ spec: type: string type: object x-kubernetes-validations: + - message: hostID is immutable + rule: self == oldSelf - message: exactly one of id or serverRef must be set rule: (has(self.id) && size(self.id) > 0) != (has(self.serverRef) && size(self.serverRef) > 0) diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index ecfcd7f93..363ae931d 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -377,14 +377,6 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT reconcileStatus := progress.NewReconcileStatus(). WithReconcileStatus(secGroupDepRS) - // Resolve hostID if specified - var resolvedHostID string - if resource.HostID != nil { - var hostIDReconcileStatus progress.ReconcileStatus - resolvedHostID, hostIDReconcileStatus = resolveHostID(ctx, actuator.k8sClient, obj, resource.HostID) - reconcileStatus = reconcileStatus.WithReconcileStatus(hostIDReconcileStatus) - } - needsReschedule, _ := reconcileStatus.NeedsReschedule() if needsReschedule { return reconcileStatus @@ -403,7 +395,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT updateOpts = baseUpdateOpts } - updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource, resolvedHostID) + updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource) updateOpts = handlePortSecurityUpdate(updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) @@ -529,7 +521,7 @@ func handleSecurityGroupRefsUpdate(updateOpts *ports.UpdateOpts, resource *resou } } -func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT, resolvedHostID string) ports.UpdateOptsBuilder { +func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT) ports.UpdateOptsBuilder { if resource.VNICType != "" { if resource.VNICType != osResource.VNICType { updateOpts = &portsbinding.UpdateOptsExt{ @@ -539,15 +531,6 @@ func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resou } } - if resolvedHostID != "" { - if resolvedHostID != osResource.HostID { - updateOpts = &portsbinding.UpdateOptsExt{ - UpdateOptsBuilder: updateOpts, - HostID: &resolvedHostID, - } - } - } - return updateOpts } diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index d6f7186c0..81a2a7cc6 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -359,7 +359,7 @@ func TestHandlePortBindingUpdate(t *testing.T) { }, } - updateOpts := handlePortBindingUpdate(&ports.UpdateOpts{}, resource, osResource, "") + updateOpts := handlePortBindingUpdate(&ports.UpdateOpts{}, resource, osResource) got, _ := needsUpdate(updateOpts) if got != tt.expectChange { diff --git a/internal/controllers/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index 6ec7e451d..fef380932 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -6,10 +6,6 @@ resourceRefs: kind: port name: port-update ref: port - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: port - name: port-update-admin - ref: portAdmin assertAll: - celExpr: "port.status.id != ''" - celExpr: "port.status.resource.createdAt != ''" @@ -17,9 +13,6 @@ assertAll: - celExpr: "port.status.resource.macAddress != ''" - celExpr: "!has(port.status.resource.fixedIPs)" - celExpr: "!has(port.status.resource.description)" - # Following the network API reference, the default value for - # hostID field is an empty string. - - celExpr: "portAdmin.status.resource.hostID == ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port @@ -43,26 +36,3 @@ status: message: OpenStack resource is up to date status: "False" reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port -metadata: - name: port-update-admin -status: - resource: - name: port-update-admin - adminStateUp: true - portSecurityEnabled: true - propagateUplinkStatus: false - revisionNumber: 1 - status: DOWN - vnicType: normal - 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/port/tests/port-update/00-minimal-resource.yaml b/internal/controllers/port/tests/port-update/00-minimal-resource.yaml index 03bbe59c3..d1242e77f 100644 --- a/internal/controllers/port/tests/port-update/00-minimal-resource.yaml +++ b/internal/controllers/port/tests/port-update/00-minimal-resource.yaml @@ -12,17 +12,3 @@ spec: portSecurity: Disabled # Need to set the default values to revert them correctly in the 02-revert-resource step. vnicType: normal ---- -# This port is intended to be used only to test fields editable -# by admin users -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port -metadata: - name: port-update-admin -spec: - cloudCredentialsRef: - cloudName: openstack-admin - secretName: openstack-clouds - managementPolicy: managed - resource: - networkRef: port-update diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index 1bcaf2d7f..c8f79187b 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -49,18 +49,3 @@ status: - type: Progressing status: "False" reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port -metadata: - name: port-update-admin -status: - resource: - hostID: devstack - conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success diff --git a/internal/controllers/port/tests/port-update/01-updated-resource.yaml b/internal/controllers/port/tests/port-update/01-updated-resource.yaml index 2af467f7c..2ad77cb94 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -20,16 +20,3 @@ spec: - tag1 vnicType: direct portSecurity: Enabled ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Port -metadata: - name: port-update-admin -spec: - cloudCredentialsRef: - cloudName: openstack-admin - secretName: openstack-clouds - managementPolicy: managed - resource: - hostID: - id: devstack diff --git a/test/apivalidations/port_test.go b/test/apivalidations/port_test.go index 3c36dea83..61ef31e00 100644 --- a/test/apivalidations/port_test.go +++ b/test/apivalidations/port_test.go @@ -123,6 +123,20 @@ var _ = Describe("ORC Port API validations", func() { Expect(applyObj(ctx, port, patch)).To(MatchError(ContainSubstring("spec.resource.vnicType: Too long: may not be longer than 64"))) }) + It("should not allow hostID to be modified", func(ctx context.Context) { + port := portStub(namespace) + patch := basePortPatch(port) + patch.Spec.WithResource(applyconfigv1alpha1.PortResourceSpec(). + WithNetworkRef(networkName). + WithHostID(applyconfigv1alpha1.HostID().WithID("host-a"))) + Expect(applyObj(ctx, port, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.PortResourceSpec(). + WithNetworkRef(networkName). + WithHostID(applyconfigv1alpha1.HostID().WithID("host-b"))) + Expect(applyObj(ctx, port, patch)).To(MatchError(ContainSubstring("hostID is immutable"))) + }) + // Note: we can't create a test for when the portSecurity is set to Inherit and the securityGroupRefs are set, because // the validation is done in the OpenStack API and not in the ORC API. The OpenStack API will return an error if // the network has port security disabled and the port has security group references. From f72c9a3458197a2838049a85fe82faf5ce118842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 6 Feb 2026 15:13:29 +0100 Subject: [PATCH 045/121] port: document potential hostID drift when bound to a server Add a note to the hostID field documentation warning that OpenStack may rebind the port to the server's actual compute host, which may differ from the specified hostID if no matching scheduler hint is used. --- api/v1alpha1/port_types.go | 5 +++++ cmd/models-schema/zz_generated.openapi.go | 2 +- config/crd/bases/openstack.k-orc.cloud_ports.yaml | 9 +++++++-- website/docs/crd-reference.md | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 9fe568189..af07d869b 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -202,6 +202,11 @@ type PortResourceSpec struct { MACAddress string `json:"macAddress,omitempty"` // hostID specifies the host where the port will be bound. + // Note that when the port is attached to a server, OpenStack may + // rebind the port to the server's actual compute host, which may + // differ from the specified hostID if no matching scheduler hint + // is used. In this case the port's status will reflect the actual + // binding host, not the value specified here. // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="hostID is immutable" HostID *HostID `json:"hostID,omitempty"` diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 285618a39..f47211048 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -4959,7 +4959,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, "hostID": { SchemaProps: spec.SchemaProps{ - Description: "hostID specifies the host where the port will be bound.", + Description: "hostID specifies the host where the port will be bound. Note that when the port is attached to a server, OpenStack may rebind the port to the server's actual compute host, which may differ from the specified hostID if no matching scheduler hint is used. In this case the port's status will reflect the actual binding host, not the value specified here.", Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID"), }, }, diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index 9bcb20f0b..7ea7190a9 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -300,8 +300,13 @@ spec: minLength: 1 type: string hostID: - description: hostID specifies the host where the port will be - bound. + description: |- + hostID specifies the host where the port will be bound. + Note that when the port is attached to a server, OpenStack may + rebind the port to the server's actual compute host, which may + differ from the specified hostID if no matching scheduler hint + is used. In this case the port's status will reflect the actual + binding host, not the value specified here. maxProperties: 1 minProperties: 1 properties: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index a62fbb623..2c2b9b8e8 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2207,7 +2207,7 @@ _Appears in:_ | `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| | `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| -| `hostID` _[HostID](#hostid)_ | hostID specifies the host where the port will be bound. | | MaxProperties: 1
MinProperties: 1
| +| `hostID` _[HostID](#hostid)_ | hostID specifies the host where the port will be bound.
Note that when the port is attached to a server, OpenStack may
rebind the port to the server's actual compute host, which may
differ from the specified hostID if no matching scheduler hint
is used. In this case the port's status will reflect the actual
binding host, not the value specified here. | | MaxProperties: 1
MinProperties: 1
| #### PortResourceStatus From cf0bdcb49dfd79f463466d1f82291364487cfb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 5 Feb 2026 16:33:11 +0100 Subject: [PATCH 046/121] Add ORC API linter to enforce API design philosophy Add a custom golangci-lint plugin that flags OpenStack ID references in spec structs, enforcing ORC's API design philosophy that spec fields should only reference ORC Kubernetes objects, not OpenStack resources directly by UUID. The noopenstackidref linter flags fields like 'ProjectID *string' in spec/filter structs and suggests using '*KubernetesNameRef' with a 'Ref' suffix instead (e.g., 'ProjectRef *KubernetesNameRef'). Status structs are exempt, as they are expected to report OpenStack UUIDs. See: https://k-orc.cloud/development/architecture/#api-design-philosophy --- .golangci.yml | 1 + Makefile | 8 +- api/v1alpha1/zz_generated.domain-resource.go | 2 +- api/v1alpha1/zz_generated.flavor-resource.go | 2 +- .../zz_generated.floatingip-resource.go | 2 +- api/v1alpha1/zz_generated.group-resource.go | 2 +- api/v1alpha1/zz_generated.image-resource.go | 2 +- api/v1alpha1/zz_generated.keypair-resource.go | 2 +- api/v1alpha1/zz_generated.network-resource.go | 2 +- api/v1alpha1/zz_generated.port-resource.go | 2 +- api/v1alpha1/zz_generated.project-resource.go | 2 +- api/v1alpha1/zz_generated.role-resource.go | 2 +- api/v1alpha1/zz_generated.router-resource.go | 2 +- .../zz_generated.securitygroup-resource.go | 2 +- api/v1alpha1/zz_generated.server-resource.go | 2 +- .../zz_generated.servergroup-resource.go | 2 +- api/v1alpha1/zz_generated.service-resource.go | 2 +- api/v1alpha1/zz_generated.subnet-resource.go | 2 +- api/v1alpha1/zz_generated.trunk-resource.go | 2 +- api/v1alpha1/zz_generated.volume-resource.go | 2 +- .../zz_generated.volumetype-resource.go | 2 +- cmd/resource-generator/data/api.template | 4 +- tools/orc-api-linter/go.mod | 18 ++ tools/orc-api-linter/go.sum | 43 +++++ .../pkg/analysis/noopenstackidref/analyzer.go | 167 ++++++++++++++++++ .../noopenstackidref/analyzer_test.go | 28 +++ .../pkg/analysis/noopenstackidref/doc.go | 57 ++++++ .../noopenstackidref/testdata/src/a/a.go | 131 ++++++++++++++ tools/orc-api-linter/plugin.go | 40 +++++ website/docs/development/api-design.md | 1 + 30 files changed, 511 insertions(+), 25 deletions(-) create mode 100644 tools/orc-api-linter/go.mod create mode 100644 tools/orc-api-linter/go.sum create mode 100644 tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go create mode 100644 tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer_test.go create mode 100644 tools/orc-api-linter/pkg/analysis/noopenstackidref/doc.go create mode 100644 tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go create mode 100644 tools/orc-api-linter/plugin.go diff --git a/.golangci.yml b/.golangci.yml index ac59808d0..0adca1fc8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -62,6 +62,7 @@ linters: - statusoptional - statussubresource - uniquemarkers + - noopenstackidref lintersConfig: conditions: isFirstField: Warn diff --git a/Makefile b/Makefile index d1b7e775b..0f1cb5a12 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,7 @@ lint: golangci-kal ## Run golangci-kal linter $(GOLANGCI_KAL) run .PHONY: lint-fix -lint-fix: golangci-kal ## Run golangci-lint linter and perform fixes +lint-fix: golangci-kal ## Run golangci-kal linter and perform fixes $(GOLANGCI_KAL) run --fix ##@ Build @@ -315,7 +315,6 @@ KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= release-0.22 GOLANGCI_LINT_VERSION ?= v2.7.2 -KAL_VERSION ?= v0.0.0-20260205134631-d65d24a9df89 MOCKGEN_VERSION ?= v0.5.0 KUTTL_VERSION ?= v0.24.0 GOVULNCHECK_VERSION ?= v1.1.4 @@ -346,8 +345,8 @@ version: $(GOLANGCI_LINT_VERSION) name: golangci-kube-api-linter destination: $(LOCALBIN) plugins: -- module: 'sigs.k8s.io/kube-api-linter' - version: $(KAL_VERSION) +- module: 'github.com/k-orc/openstack-resource-controller/v2/tools/orc-api-linter' + path: ./tools/orc-api-linter endef export custom-gcl @@ -357,6 +356,7 @@ CUSTOM_GCL_FILE ?= $(shell pwd)/.custom-gcl.yml golangci-kal: $(GOLANGCI_KAL) $(GOLANGCI_KAL): $(LOCALBIN) $(GOLANGCI_LINT) $(file >$(CUSTOM_GCL_FILE),$(custom-gcl)) + cd tools/orc-api-linter && go mod tidy $(GOLANGCI_LINT) custom .PHONY: mockgen diff --git a/api/v1alpha1/zz_generated.domain-resource.go b/api/v1alpha1/zz_generated.domain-resource.go index 07505e07b..ae2e5fc4e 100644 --- a/api/v1alpha1/zz_generated.domain-resource.go +++ b/api/v1alpha1/zz_generated.domain-resource.go @@ -32,7 +32,7 @@ type DomainImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.flavor-resource.go b/api/v1alpha1/zz_generated.flavor-resource.go index c3ca4a8b6..6ae9d1fd8 100644 --- a/api/v1alpha1/zz_generated.flavor-resource.go +++ b/api/v1alpha1/zz_generated.flavor-resource.go @@ -32,7 +32,7 @@ type FlavorImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.floatingip-resource.go b/api/v1alpha1/zz_generated.floatingip-resource.go index 6d7501526..d502e9b65 100644 --- a/api/v1alpha1/zz_generated.floatingip-resource.go +++ b/api/v1alpha1/zz_generated.floatingip-resource.go @@ -32,7 +32,7 @@ type FloatingIPImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.group-resource.go b/api/v1alpha1/zz_generated.group-resource.go index 51e19eedd..653bea813 100644 --- a/api/v1alpha1/zz_generated.group-resource.go +++ b/api/v1alpha1/zz_generated.group-resource.go @@ -32,7 +32,7 @@ type GroupImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.image-resource.go b/api/v1alpha1/zz_generated.image-resource.go index a74b4cd38..e9a65eff8 100644 --- a/api/v1alpha1/zz_generated.image-resource.go +++ b/api/v1alpha1/zz_generated.image-resource.go @@ -32,7 +32,7 @@ type ImageImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.keypair-resource.go b/api/v1alpha1/zz_generated.keypair-resource.go index 4703a9f17..57d13fde6 100644 --- a/api/v1alpha1/zz_generated.keypair-resource.go +++ b/api/v1alpha1/zz_generated.keypair-resource.go @@ -32,7 +32,7 @@ type KeyPairImport struct { // The ORC object will enter an error state if the resource does not exist. // +kubebuilder:validation:MaxLength:=1024 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.network-resource.go b/api/v1alpha1/zz_generated.network-resource.go index 5e0248b92..bc60852dc 100644 --- a/api/v1alpha1/zz_generated.network-resource.go +++ b/api/v1alpha1/zz_generated.network-resource.go @@ -32,7 +32,7 @@ type NetworkImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.port-resource.go b/api/v1alpha1/zz_generated.port-resource.go index 5f707e564..8b1c25ca4 100644 --- a/api/v1alpha1/zz_generated.port-resource.go +++ b/api/v1alpha1/zz_generated.port-resource.go @@ -32,7 +32,7 @@ type PortImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.project-resource.go b/api/v1alpha1/zz_generated.project-resource.go index 473442d35..33fce32e2 100644 --- a/api/v1alpha1/zz_generated.project-resource.go +++ b/api/v1alpha1/zz_generated.project-resource.go @@ -32,7 +32,7 @@ type ProjectImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go index 31a915d25..5891a418b 100644 --- a/api/v1alpha1/zz_generated.role-resource.go +++ b/api/v1alpha1/zz_generated.role-resource.go @@ -32,7 +32,7 @@ type RoleImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.router-resource.go b/api/v1alpha1/zz_generated.router-resource.go index 83b0681de..68d83bd53 100644 --- a/api/v1alpha1/zz_generated.router-resource.go +++ b/api/v1alpha1/zz_generated.router-resource.go @@ -32,7 +32,7 @@ type RouterImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.securitygroup-resource.go b/api/v1alpha1/zz_generated.securitygroup-resource.go index f378c1b29..ac0a921a6 100644 --- a/api/v1alpha1/zz_generated.securitygroup-resource.go +++ b/api/v1alpha1/zz_generated.securitygroup-resource.go @@ -32,7 +32,7 @@ type SecurityGroupImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.server-resource.go b/api/v1alpha1/zz_generated.server-resource.go index d00f9f934..011c5896d 100644 --- a/api/v1alpha1/zz_generated.server-resource.go +++ b/api/v1alpha1/zz_generated.server-resource.go @@ -32,7 +32,7 @@ type ServerImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.servergroup-resource.go b/api/v1alpha1/zz_generated.servergroup-resource.go index 88f875511..9f478e276 100644 --- a/api/v1alpha1/zz_generated.servergroup-resource.go +++ b/api/v1alpha1/zz_generated.servergroup-resource.go @@ -32,7 +32,7 @@ type ServerGroupImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.service-resource.go b/api/v1alpha1/zz_generated.service-resource.go index 1b9275360..0c0182818 100644 --- a/api/v1alpha1/zz_generated.service-resource.go +++ b/api/v1alpha1/zz_generated.service-resource.go @@ -32,7 +32,7 @@ type ServiceImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.subnet-resource.go b/api/v1alpha1/zz_generated.subnet-resource.go index c6fcc4090..0151f3064 100644 --- a/api/v1alpha1/zz_generated.subnet-resource.go +++ b/api/v1alpha1/zz_generated.subnet-resource.go @@ -32,7 +32,7 @@ type SubnetImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.trunk-resource.go b/api/v1alpha1/zz_generated.trunk-resource.go index 00aefdd24..eb4c5e844 100644 --- a/api/v1alpha1/zz_generated.trunk-resource.go +++ b/api/v1alpha1/zz_generated.trunk-resource.go @@ -32,7 +32,7 @@ type TrunkImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.volume-resource.go b/api/v1alpha1/zz_generated.volume-resource.go index 839df23da..9451474fb 100644 --- a/api/v1alpha1/zz_generated.volume-resource.go +++ b/api/v1alpha1/zz_generated.volume-resource.go @@ -32,7 +32,7 @@ type VolumeImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/api/v1alpha1/zz_generated.volumetype-resource.go b/api/v1alpha1/zz_generated.volumetype-resource.go index 4ae6b2a85..5f583a2bd 100644 --- a/api/v1alpha1/zz_generated.volumetype-resource.go +++ b/api/v1alpha1/zz_generated.volumetype-resource.go @@ -32,7 +32,7 @@ type VolumeTypeImport struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + 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 diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index ecdc787b2..9abb00d7a 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -32,7 +32,7 @@ type {{ .Name }}Import struct { // The ORC object will enter an error state if the resource does not exist. // +kubebuilder:validation:MaxLength:=1024 // +optional - ID *string `json:"id,omitempty"` + ID *string `json:"id,omitempty"` //nolint:kubeapilinter {{- else }} // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. @@ -40,7 +40,7 @@ type {{ .Name }}Import struct { // +kubebuilder:validation:Format:=uuid // +kubebuilder:validation:MaxLength:=36 // +optional - ID *string `json:"id,omitempty"` + ID *string `json:"id,omitempty"` //nolint:kubeapilinter {{- end }} // filter contains a resource query which is expected to return a single diff --git a/tools/orc-api-linter/go.mod b/tools/orc-api-linter/go.mod new file mode 100644 index 000000000..06dc79143 --- /dev/null +++ b/tools/orc-api-linter/go.mod @@ -0,0 +1,18 @@ +module github.com/k-orc/openstack-resource-controller/v2/tools/orc-api-linter + +go 1.24.0 + +require ( + golang.org/x/tools v0.41.0 + sigs.k8s.io/kube-api-linter v0.0.0-20260205134631-d65d24a9df89 +) + +require ( + github.com/golangci/plugin-module-register v0.1.2 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/sync v0.19.0 // indirect + k8s.io/apimachinery v0.32.3 // indirect + k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/tools/orc-api-linter/go.sum b/tools/orc-api-linter/go.sum new file mode 100644 index 000000000..fce1ec281 --- /dev/null +++ b/tools/orc-api-linter/go.sum @@ -0,0 +1,43 @@ +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= +github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= +github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= +github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= +github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= +k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/kube-api-linter v0.0.0-20260205134631-d65d24a9df89 h1:QuWBEzbBkQyuwWPKDEaUBGr8QdHilkc4CdJYCeU1SIo= +sigs.k8s.io/kube-api-linter v0.0.0-20260205134631-d65d24a9df89/go.mod h1:5mP60UakkCye+eOcZ5p98VnV2O49qreW1gq9TdsUf7Q= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go new file mode 100644 index 000000000..39c190051 --- /dev/null +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go @@ -0,0 +1,167 @@ +/* +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 noopenstackidref + +import ( + "go/ast" + "regexp" + "slices" + "strings" + + "golang.org/x/tools/go/analysis" + "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags" + "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector" + "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers" + "sigs.k8s.io/kube-api-linter/pkg/analysis/initializer" + "sigs.k8s.io/kube-api-linter/pkg/analysis/registry" +) + +const ( + name = "noopenstackidref" + doc = `Flags OpenStack ID references in spec structs. + +ORC's API design philosophy states that spec fields should only reference +ORC Kubernetes objects, not OpenStack resources directly by UUID. + +Fields ending with 'ID' (like ProjectID, NetworkID) in spec structs should +instead use KubernetesNameRef type with a 'Ref' suffix (like ProjectRef, NetworkRef). + +See: https://k-orc.cloud/development/api-design/` +) + +// openstackIDPattern matches field names that end with "ID" and are likely +// references to OpenStack resources by UUID. These should instead use +// KubernetesNameRef with a "Ref" suffix to reference ORC objects. +var openstackIDPattern = regexp.MustCompile(`ID$`) + +// excludedIDPatterns contains field name patterns that end in "ID" but are +// not OpenStack resource references. +var excludedIDPatterns = []string{ + "SegmentationID", // VLAN segmentation ID, not an OpenStack resource +} + +// excludedStructs contains struct names that should not be checked even though +// they don't have "Status" in their name. These are typically nested types used +// exclusively within status structs. +var excludedStructs = []string{ + "ServerInterfaceFixedIP", // Used only in ServerInterfaceStatus.FixedIPs +} + +// Analyzer is the analyzer for the noopenstackidref linter. +var Analyzer = &analysis.Analyzer{ + Name: name, + Doc: doc, + Run: run, + Requires: []*analysis.Analyzer{inspector.Analyzer}, +} + +func init() { + registry.DefaultRegistry().RegisterLinter(initializer.NewInitializer( + name, + Analyzer, + false, // not enabled by default - must be explicitly enabled + )) +} + +func run(pass *analysis.Pass) (any, error) { + inspect, ok := pass.ResultOf[inspector.Analyzer].(inspector.Inspector) + if !ok { + return nil, nil + } + + inspect.InspectFieldsIncludingListTypes(func(field *ast.Field, _ extractjsontags.FieldTagInfo, _ markers.Markers, qualifiedFieldName string) { + checkField(pass, field, qualifiedFieldName) + }) + + return nil, nil +} + +func checkField(pass *analysis.Pass, field *ast.Field, qualifiedFieldName string) { + // qualifiedFieldName is in the form "StructName.FieldName" + parts := strings.SplitN(qualifiedFieldName, ".", 2) + if len(parts) != 2 { + return + } + + structName := parts[0] + fieldName := parts[1] + + // Only check spec-related structs, not status structs + if !isSpecStruct(structName) { + return + } + + // Check if field name matches OpenStack ID pattern + if !openstackIDPattern.MatchString(fieldName) { + return + } + + // Check if field name is in the exclusion list + if slices.Contains(excludedIDPatterns, fieldName) { + return + } + + // Allow *KubernetesNameRef type (correct type, even if name ends in ID) + if isKubernetesNameRefType(field.Type) { + return + } + + suggestedRef := strings.TrimSuffix(fieldName, "ID") + "Ref" + pass.Reportf(field.Pos(), + "field %s references OpenStack resource by ID in spec; "+ + "use *KubernetesNameRef with %s instead; "+ + "see https://k-orc.cloud/development/api-design/", + qualifiedFieldName, suggestedRef) +} + +// isSpecStruct returns true if the struct name indicates it's a spec-related struct +// (where OpenStack ID references should be flagged), not a status struct +// (where OpenStack IDs are expected and valid). +func isSpecStruct(structName string) bool { + // Status structs are allowed to have OpenStack IDs + if strings.HasSuffix(structName, "Status") || + strings.Contains(structName, "Status") { + return false + } + + // Check excluded structs (nested types used only in status contexts) + if slices.Contains(excludedStructs, structName) { + return false + } + + // All other structs should use KubernetesNameRef for references + return true +} + +// isKubernetesNameRefType checks if the expression is KubernetesNameRef or *KubernetesNameRef. +// This is the only acceptable type for fields that might look like ID references. +func isKubernetesNameRefType(expr ast.Expr) bool { + // Check for *KubernetesNameRef + if starExpr, ok := expr.(*ast.StarExpr); ok { + if ident, ok := starExpr.X.(*ast.Ident); ok { + return ident.Name == "KubernetesNameRef" + } + return false + } + + // Check for KubernetesNameRef (non-pointer) + if ident, ok := expr.(*ast.Ident); ok { + return ident.Name == "KubernetesNameRef" + } + + return false +} diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer_test.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer_test.go new file mode 100644 index 000000000..ba8ee5e79 --- /dev/null +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer_test.go @@ -0,0 +1,28 @@ +/* +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 noopenstackidref + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestAnalyzer(t *testing.T) { + testdata := analysistest.TestData() + analysistest.Run(t, testdata, Analyzer, "a") +} diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/doc.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/doc.go new file mode 100644 index 000000000..6e0938616 --- /dev/null +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/doc.go @@ -0,0 +1,57 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package noopenstackidref provides a linter that enforces ORC's API design +// philosophy of referencing ORC Kubernetes objects rather than OpenStack +// resources directly by UUID. +// +// # Overview +// +// ORC (OpenStack Resource Controller) manages OpenStack resources through +// Kubernetes custom resources. The API design philosophy states that spec +// fields should only reference other ORC objects, not OpenStack resources +// directly by UUID. +// +// # What this linter checks +// +// The linter flags fields in spec-related structs that: +// - Have names matching OpenStack resource ID patterns (e.g., ProjectID, NetworkID) +// - Are of type *string +// +// These should instead use *KubernetesNameRef with a 'Ref' suffix. +// +// # Examples +// +// Bad (will be flagged): +// +// type UserResourceSpec struct { +// DefaultProjectID *string `json:"defaultProjectID,omitempty"` +// } +// +// Good (correct pattern): +// +// type UserResourceSpec struct { +// DefaultProjectRef *KubernetesNameRef `json:"defaultProjectRef,omitempty"` +// } +// +// # Status structs are exempt +// +// Fields in status structs (ending with 'Status' or 'ResourceStatus') are +// allowed to have OpenStack IDs, as they report what OpenStack returned. +// +// See https://k-orc.cloud/development/architecture/#api-design-philosophy +// for more details on ORC's API design philosophy. +package noopenstackidref diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go new file mode 100644 index 000000000..501001210 --- /dev/null +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.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. +*/ + +package a + +// KubernetesNameRef is a reference to a Kubernetes object by name. +type KubernetesNameRef string + +// HostID is a custom struct type (simulating ORC's HostID pattern). +// The bare ID field inside is flagged and requires a nolint comment if intentional. +type HostID struct { + ID string `json:"id,omitempty"` // want `field HostID.ID references OpenStack resource by ID in spec` + ServerRef KubernetesNameRef `json:"serverRef,omitempty"` +} + +// ---- Spec structs: OpenStack IDs should be flagged ---- + +// UserResourceSpec is a spec struct that should be checked. +type UserResourceSpec struct { + // Name is fine, not an OpenStack ID reference. + Name *string `json:"name,omitempty"` + + DefaultProjectID *string `json:"defaultProjectID,omitempty"` // want `field UserResourceSpec.DefaultProjectID references OpenStack resource by ID in spec` + + // DomainRef is good - uses KubernetesNameRef. + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` +} + +// PortResourceSpec has multiple violations. +type PortResourceSpec struct { + NetworkID *string `json:"networkID,omitempty"` // want `field PortResourceSpec.NetworkID references OpenStack resource by ID in spec` + + SubnetID *string `json:"subnetID,omitempty"` // want `field PortResourceSpec.SubnetID references OpenStack resource by ID in spec` + + // ProjectRef is correct. + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` +} + +// ServerSpec tests shortened Spec suffix. +type ServerSpec struct { + ImageID *string `json:"imageID,omitempty"` // want `field ServerSpec.ImageID references OpenStack resource by ID in spec` + + FlavorID *string `json:"flavorID,omitempty"` // want `field ServerSpec.FlavorID references OpenStack resource by ID in spec` +} + +// ---- Filter structs: OpenStack IDs should also be flagged ---- + +// NetworkFilter is a filter struct that should be checked. +type NetworkFilter struct { + ProjectID *string `json:"projectID,omitempty"` // want `field NetworkFilter.ProjectID references OpenStack resource by ID in spec` + + // Name is fine. + Name *string `json:"name,omitempty"` +} + +// ---- Status structs: OpenStack IDs are allowed ---- + +// UserResourceStatus is a status struct where OpenStack IDs are expected. +type UserResourceStatus struct { + // DefaultProjectID is allowed in status - it reports what OpenStack returned. + DefaultProjectID string `json:"defaultProjectID,omitempty"` + + // DomainID is allowed in status. + DomainID string `json:"domainID,omitempty"` +} + +// PortStatus tests shortened Status suffix. +type PortStatus struct { + // NetworkID is allowed in status. + NetworkID string `json:"networkID,omitempty"` +} + +// ---- Nested types used in specs: should also be flagged ---- + +// ServerBlockDevice is a nested type used in ServerResourceSpec. +type ServerBlockDevice struct { + VolumeID *string `json:"volumeID,omitempty"` // want `field ServerBlockDevice.VolumeID references OpenStack resource by ID in spec` + + // Device is fine. + Device *string `json:"device,omitempty"` +} + +// SecurityGroupRule is a nested type. +type SecurityGroupRule struct { + RemoteGroupID *string `json:"remoteGroupID,omitempty"` // want `field SecurityGroupRule.RemoteGroupID references OpenStack resource by ID in spec` +} + +// ---- Edge cases ---- + +// NonPointerIDSpec has non-pointer ID fields which should also be flagged. +type NonPointerIDSpec struct { + ProjectID string `json:"projectID,omitempty"` // want `field NonPointerIDSpec.ProjectID references OpenStack resource by ID in spec` +} + +// UnrelatedIDStruct has ID fields that don't look like OpenStack resources, +// but they are still flagged because any *ID pattern could be a reference. +// Users should add //nolint:noopenstackidref if these are intentional. +type UnrelatedIDStruct struct { + ExternalID *string `json:"externalID,omitempty"` // want `field UnrelatedIDStruct.ExternalID references OpenStack resource by ID in spec` +} + +// StructTypeIDSpec tests that struct-typed ID fields are also flagged. +type StructTypeIDSpec struct { + HostID *HostID `json:"hostID,omitempty"` // want `field StructTypeIDSpec.HostID references OpenStack resource by ID in spec` +} + +// BareIDSpec tests that bare "ID" field is also flagged. +// Use //nolint:noopenstackidref for legitimate cases like spec.import.id. +type BareIDSpec struct { + ID *string `json:"id,omitempty"` // want `field BareIDSpec.ID references OpenStack resource by ID in spec` +} + +// WrongNameCorrectTypeSpec tests that *KubernetesNameRef with wrong name is allowed. +// This is acceptable because the type is correct even if naming is unconventional. +type WrongNameCorrectTypeSpec struct { + // ProjectID with *KubernetesNameRef type is allowed (type takes precedence). + ProjectID *KubernetesNameRef `json:"projectID,omitempty"` +} diff --git a/tools/orc-api-linter/plugin.go b/tools/orc-api-linter/plugin.go new file mode 100644 index 000000000..d7d0e22bb --- /dev/null +++ b/tools/orc-api-linter/plugin.go @@ -0,0 +1,40 @@ +/* +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 orcapilinter is a golangci-lint plugin that extends kube-api-linter +// with ORC-specific API design rules. +// +// It imports the base kube-api-linter plugin and registers additional ORC-specific +// linters with the registry. This allows all linters to be configured through +// the kubeapilinter section in .golangci.yml. +package orcapilinter + +import ( + pluginbase "sigs.k8s.io/kube-api-linter/pkg/plugin/base" + + // Import the default kube-api-linter linters. + _ "sigs.k8s.io/kube-api-linter/pkg/registration" + + // Import ORC-specific linters to register them with the registry. + _ "github.com/k-orc/openstack-resource-controller/v2/tools/orc-api-linter/pkg/analysis/noopenstackidref" +) + +// New is the entrypoint for the plugin. +// We use the base kube-api-linter plugin which will include both the standard +// KAL linters and our ORC-specific linters registered via init(). +// +//nolint:gochecknoglobals +var New = pluginbase.New diff --git a/website/docs/development/api-design.md b/website/docs/development/api-design.md index 2fdd30b2b..5f867fb67 100644 --- a/website/docs/development/api-design.md +++ b/website/docs/development/api-design.md @@ -83,3 +83,4 @@ You should update `examples/components/kustomizeconfig/kustomizeconfig.yaml` wit * Do not use unsigned integers: use `intN` with a kubebuilder marker validating for a minimum of 0. * Optional fields should have the `omitempty` tag. * Optional fields should be pointers, unless their zero-value is also the OpenStack default, or we can be very confident that we will never need to distinguish between empty and unset values. e.g. Will we ever want to set a value explicitly to the empty string? +* ResourceSpec and Filter fields must reference other ORC objects using `*KubernetesNameRef` with a `Ref` suffix (e.g., `ProjectRef`), not OpenStack resources directly by UUID (e.g., `ProjectID *string`). Exceptions include bare `ID` fields (used for `spec.import.id`) and non-resource IDs like `SegmentationID`. From 4c77b58f0b9543cccfaa6d3e85533aa506d5e399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 6 Feb 2026 10:37:20 +0100 Subject: [PATCH 047/121] Extend noopenstackidref linter to flag plural *IDs fields Fields like NetworkIDs, SubnetIDs should use NetworkRefs, SubnetRefs instead. This ensures consistency with the singular form (NetworkID -> NetworkRef). --- .../pkg/analysis/noopenstackidref/analyzer.go | 21 ++++++++++++------- .../noopenstackidref/testdata/src/a/a.go | 18 ++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go index 39c190051..a217498dd 100644 --- a/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go @@ -37,18 +37,18 @@ const ( ORC's API design philosophy states that spec fields should only reference ORC Kubernetes objects, not OpenStack resources directly by UUID. -Fields ending with 'ID' (like ProjectID, NetworkID) in spec structs should -instead use KubernetesNameRef type with a 'Ref' suffix (like ProjectRef, NetworkRef). +Fields ending with 'ID' or 'IDs' (like ProjectID, NetworkIDs) in spec structs should +instead use KubernetesNameRef type with a 'Ref' or 'Refs' suffix (like ProjectRef, NetworkRefs). See: https://k-orc.cloud/development/api-design/` ) -// openstackIDPattern matches field names that end with "ID" and are likely +// openstackIDPattern matches field names that end with "ID" or "IDs" and are likely // references to OpenStack resources by UUID. These should instead use -// KubernetesNameRef with a "Ref" suffix to reference ORC objects. -var openstackIDPattern = regexp.MustCompile(`ID$`) +// KubernetesNameRef with a "Ref" or "Refs" suffix to reference ORC objects. +var openstackIDPattern = regexp.MustCompile(`IDs?$`) -// excludedIDPatterns contains field name patterns that end in "ID" but are +// excludedIDPatterns contains field name patterns that end in "ID" or "IDs" but are // not OpenStack resource references. var excludedIDPatterns = []string{ "SegmentationID", // VLAN segmentation ID, not an OpenStack resource @@ -120,7 +120,14 @@ func checkField(pass *analysis.Pass, field *ast.Field, qualifiedFieldName string return } - suggestedRef := strings.TrimSuffix(fieldName, "ID") + "Ref" + // Generate the suggested Ref/Refs name based on singular/plural + var suggestedRef string + if strings.HasSuffix(fieldName, "IDs") { + suggestedRef = strings.TrimSuffix(fieldName, "IDs") + "Refs" + } else { + suggestedRef = strings.TrimSuffix(fieldName, "ID") + "Ref" + } + pass.Reportf(field.Pos(), "field %s references OpenStack resource by ID in spec; "+ "use *KubernetesNameRef with %s instead; "+ diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go index 501001210..ce899c2b0 100644 --- a/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go @@ -129,3 +129,21 @@ type WrongNameCorrectTypeSpec struct { // ProjectID with *KubernetesNameRef type is allowed (type takes precedence). ProjectID *KubernetesNameRef `json:"projectID,omitempty"` } + +// ---- Plural ID fields: should also be flagged ---- + +// PluralIDsSpec tests that plural IDs fields are flagged. +type PluralIDsSpec struct { + NetworkIDs []string `json:"networkIDs,omitempty"` // want `field PluralIDsSpec.NetworkIDs references OpenStack resource by ID in spec` + + SubnetIDs []string `json:"subnetIDs,omitempty"` // want `field PluralIDsSpec.SubnetIDs references OpenStack resource by ID in spec` + + // SecurityGroupRefs is correct - uses the Refs suffix. + SecurityGroupRefs []KubernetesNameRef `json:"securityGroupRefs,omitempty"` +} + +// PluralIDsStatus tests that plural IDs in status are allowed. +type PluralIDsStatus struct { + // NetworkIDs is allowed in status. + NetworkIDs []string `json:"networkIDs,omitempty"` +} From 13afb05783f03198b83479e770d8616379448d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 6 Feb 2026 10:48:53 +0100 Subject: [PATCH 048/121] Flag *Ref/*Refs fields that don't use KubernetesNameRef type Fields ending in Ref or Refs should use KubernetesNameRef type to reference ORC objects. This catches cases where the naming convention is correct but the type is wrong (e.g., SecurityGroupRefs []OpenStackName should be []KubernetesNameRef). CloudCredentialsRef is excluded as it intentionally uses a different type. --- .../pkg/analysis/noopenstackidref/analyzer.go | 42 +++++++++++++++++++ .../noopenstackidref/testdata/src/a/a.go | 38 +++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go index a217498dd..69a106479 100644 --- a/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/analyzer.go @@ -40,6 +40,9 @@ ORC Kubernetes objects, not OpenStack resources directly by UUID. Fields ending with 'ID' or 'IDs' (like ProjectID, NetworkIDs) in spec structs should instead use KubernetesNameRef type with a 'Ref' or 'Refs' suffix (like ProjectRef, NetworkRefs). +Additionally, fields ending with 'Ref' or 'Refs' must use the KubernetesNameRef type, +not other types like OpenStackName or string. + See: https://k-orc.cloud/development/api-design/` ) @@ -48,12 +51,22 @@ See: https://k-orc.cloud/development/api-design/` // KubernetesNameRef with a "Ref" or "Refs" suffix to reference ORC objects. var openstackIDPattern = regexp.MustCompile(`IDs?$`) +// refPattern matches field names that end with "Ref" or "Refs". +// These fields should use KubernetesNameRef type. +var refPattern = regexp.MustCompile(`Refs?$`) + // excludedIDPatterns contains field name patterns that end in "ID" or "IDs" but are // not OpenStack resource references. var excludedIDPatterns = []string{ "SegmentationID", // VLAN segmentation ID, not an OpenStack resource } +// excludedRefPatterns contains field name patterns that end in "Ref" or "Refs" but +// intentionally use a different type than KubernetesNameRef. +var excludedRefPatterns = []string{ + "CloudCredentialsRef", // References a credentials secret, not an ORC object +} + // excludedStructs contains struct names that should not be checked even though // they don't have "Status" in their name. These are typically nested types used // exclusively within status structs. @@ -105,6 +118,22 @@ func checkField(pass *analysis.Pass, field *ast.Field, qualifiedFieldName string return } + // Check if field name ends in Ref/Refs but uses wrong type + if refPattern.MatchString(fieldName) { + // Check if field name is in the Ref exclusion list + if slices.Contains(excludedRefPatterns, fieldName) { + return + } + + if !isKubernetesNameRefTypeOrSlice(field.Type) { + pass.Reportf(field.Pos(), + "field %s has Ref suffix but does not use KubernetesNameRef type; "+ + "see https://k-orc.cloud/development/api-design/", + qualifiedFieldName) + } + return + } + // Check if field name matches OpenStack ID pattern if !openstackIDPattern.MatchString(fieldName) { return @@ -172,3 +201,16 @@ func isKubernetesNameRefType(expr ast.Expr) bool { return false } + +// isKubernetesNameRefTypeOrSlice checks if the expression is KubernetesNameRef, +// *KubernetesNameRef, or []KubernetesNameRef. This is used for Ref/Refs fields +// which may be singular or plural. +func isKubernetesNameRefTypeOrSlice(expr ast.Expr) bool { + // Check for []KubernetesNameRef + if arrayType, ok := expr.(*ast.ArrayType); ok { + return isKubernetesNameRefType(arrayType.Elt) + } + + // Check for KubernetesNameRef or *KubernetesNameRef + return isKubernetesNameRefType(expr) +} diff --git a/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go b/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go index ce899c2b0..7d787400f 100644 --- a/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go +++ b/tools/orc-api-linter/pkg/analysis/noopenstackidref/testdata/src/a/a.go @@ -147,3 +147,41 @@ type PluralIDsStatus struct { // NetworkIDs is allowed in status. NetworkIDs []string `json:"networkIDs,omitempty"` } + +// ---- Ref/Refs fields with wrong type: should be flagged ---- + +// OpenStackName simulates the ORC OpenStackName type (wrong type for Refs). +type OpenStackName string + +// WrongTypeRefSpec tests that Ref fields with wrong type are flagged. +type WrongTypeRefSpec struct { + // ProjectRef with *string type is wrong - should use *KubernetesNameRef. + ProjectRef *string `json:"projectRef,omitempty"` // want `field WrongTypeRefSpec.ProjectRef has Ref suffix but does not use KubernetesNameRef type` + + // NetworkRef with OpenStackName type is wrong - should use KubernetesNameRef. + NetworkRef OpenStackName `json:"networkRef,omitempty"` // want `field WrongTypeRefSpec.NetworkRef has Ref suffix but does not use KubernetesNameRef type` + + // SubnetRef is correct - uses KubernetesNameRef. + SubnetRef KubernetesNameRef `json:"subnetRef,omitempty"` + + // RouterRef is correct - uses *KubernetesNameRef. + RouterRef *KubernetesNameRef `json:"routerRef,omitempty"` +} + +// WrongTypeRefsSpec tests that plural Refs fields with wrong type are flagged. +type WrongTypeRefsSpec struct { + // SecurityGroupRefs with []OpenStackName type is wrong - should use []KubernetesNameRef. + SecurityGroupRefs []OpenStackName `json:"securityGroupRefs,omitempty"` // want `field WrongTypeRefsSpec.SecurityGroupRefs has Ref suffix but does not use KubernetesNameRef type` + + // NetworkRefs with []string type is wrong - should use []KubernetesNameRef. + NetworkRefs []string `json:"networkRefs,omitempty"` // want `field WrongTypeRefsSpec.NetworkRefs has Ref suffix but does not use KubernetesNameRef type` + + // SubnetRefs is correct - uses []KubernetesNameRef. + SubnetRefs []KubernetesNameRef `json:"subnetRefs,omitempty"` +} + +// WrongTypeRefsStatus tests that Refs in status with wrong type are allowed. +type WrongTypeRefsStatus struct { + // SecurityGroupRefs is allowed in status even with wrong type. + SecurityGroupRefs []OpenStackName `json:"securityGroupRefs,omitempty"` +} From 35fa1a1ed8323c35f3ff1f6b872092d1914357cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 6 Feb 2026 11:32:00 +0100 Subject: [PATCH 049/121] Add nolint comments for intentional API design choices - HostID.ID: Allows raw hypervisor host ID as alternative to ServerRef - PortResourceSpec.HostID: The HostID struct intentionally provides both options - SecurityGroupRefs: Known issue #438, breaking change planned for next API version --- api/v1alpha1/port_types.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index af07d869b..884411a07 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -59,7 +59,7 @@ type HostID struct { // This is mutually exclusive with serverRef. // +kubebuilder:validation:MaxLength=36 // +optional - ID string `json:"id,omitempty"` + ID string `json:"id,omitempty"` //nolint:kubeapilinter // intentionally allow raw ID // serverRef is a reference to an ORC Server resource from which to // retrieve the hostID for port binding. The hostID will be read from @@ -167,7 +167,7 @@ type PortResourceSpec struct { // +kubebuilder:validation:MaxItems:=64 // +listType=set // +optional - SecurityGroupRefs []OpenStackName `json:"securityGroupRefs,omitempty"` + SecurityGroupRefs []OpenStackName `json:"securityGroupRefs,omitempty"` //nolint:kubeapilinter // https://github.com/k-orc/openstack-resource-controller/issues/438 // vnicType specifies the type of vNIC which this port should be // attached to. This is used to determine which mechanism driver(s) to @@ -209,7 +209,7 @@ type PortResourceSpec struct { // binding host, not the value specified here. // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="hostID is immutable" - HostID *HostID `json:"hostID,omitempty"` + HostID *HostID `json:"hostID,omitempty"` //nolint:kubeapilinter // HostID provides both raw ID and ServerRef options } type PortResourceStatus struct { From a766ecf951c52dde67bad713c42aaa28f866915b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 6 Feb 2026 11:46:02 +0100 Subject: [PATCH 050/121] Clarify listType guidance for struct vs primitive arrays Update API design documentation to align with ssatags linter behavior: listType=set is discouraged for object arrays due to SSA merge issues, so recommend listType=map for structs and listType=set only for primitives. --- website/docs/development/api-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/development/api-design.md b/website/docs/development/api-design.md index 5f867fb67..79597cb84 100644 --- a/website/docs/development/api-design.md +++ b/website/docs/development/api-design.md @@ -32,7 +32,7 @@ This is located at `spec.resource` in the base object. It is only defined for [m * Where relevant, the `ResourceSpec` should include a `name` field to allow object name to be overridden. * All fields should use pre-defined validated types where possible, e.g. `OpenStackName`, `NeutronDescription`, `IPvAny`. -* Lists should have type `set` or `map` where possible, but `atomic` lists may be necessary where a struct has no merge key. +* Lists of structs should use `listType=map` with an appropriate `listMapKey` where possible. `listType=set` should only be used for lists of primitives (e.g., strings). `listType=atomic` may be necessary where a struct has no suitable merge key. ### ResourceStatus From 4b29e215736bd3335469d969c9b86940c8127ee8 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Sat, 29 Nov 2025 18:26:29 -0300 Subject: [PATCH 051/121] endpoint: generate code with scaffolding tool go run ./cmd/scaffold-controller \ -interactive=false \ -kind Endpoint \ -gophercloud-client NewIdentityV3 \ -gophercloud-module github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints \ -required-create-dependency Service \ -import-dependency Service Signed-off-by: Winicius Silva --- api/v1alpha1/endpoint_types.go | 88 +++++ .../zz_generated.endpoint-resource.go | 177 ++++++++++ .../openstack.k-orc.cloud_endpoints.yaml | 322 ++++++++++++++++++ .../samples/openstack_v1alpha1_endpoint.yaml | 14 + internal/controllers/endpoint/actuator.go | 271 +++++++++++++++ .../controllers/endpoint/actuator_test.go | 119 +++++++ internal/controllers/endpoint/controller.go | 114 +++++++ internal/controllers/endpoint/status.go | 64 ++++ .../tests/endpoint-create-full/00-assert.yaml | 33 ++ .../00-create-resource.yaml | 29 ++ .../tests/endpoint-create-full/00-secret.yaml | 6 + .../tests/endpoint-create-full/README.md | 11 + .../endpoint-create-minimal/00-assert.yaml | 32 ++ .../00-create-resource.yaml | 28 ++ .../endpoint-create-minimal/00-secret.yaml | 6 + .../endpoint-create-minimal/01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../tests/endpoint-create-minimal/README.md | 15 + .../tests/endpoint-dependency/00-assert.yaml | 30 ++ .../00-create-resources-missing-deps.yaml | 42 +++ .../tests/endpoint-dependency/00-secret.yaml | 6 + .../tests/endpoint-dependency/01-assert.yaml | 30 ++ .../01-create-dependencies.yaml | 19 ++ .../tests/endpoint-dependency/02-assert.yaml | 17 + .../02-delete-dependencies.yaml | 9 + .../tests/endpoint-dependency/03-assert.yaml | 9 + .../03-delete-resources.yaml | 10 + .../tests/endpoint-dependency/README.md | 21 ++ .../endpoint-import-dependency/00-assert.yaml | 17 + .../00-import-resource.yaml | 26 ++ .../endpoint-import-dependency/00-secret.yaml | 6 + .../endpoint-import-dependency/01-assert.yaml | 32 ++ .../01-create-trap-resource.yaml | 28 ++ .../endpoint-import-dependency/02-assert.yaml | 34 ++ .../02-create-resource.yaml | 27 ++ .../endpoint-import-dependency/03-assert.yaml | 6 + .../03-delete-import-dependencies.yaml | 7 + .../endpoint-import-dependency/04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../endpoint-import-dependency/README.md | 29 ++ .../endpoint-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 43 +++ .../endpoint-import-error/00-secret.yaml | 6 + .../endpoint-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/endpoint-import-error/README.md | 13 + .../tests/endpoint-import/00-assert.yaml | 15 + .../endpoint-import/00-import-resource.yaml | 15 + .../tests/endpoint-import/00-secret.yaml | 6 + .../tests/endpoint-import/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 31 ++ .../tests/endpoint-import/02-assert.yaml | 33 ++ .../endpoint-import/02-create-resource.yaml | 28 ++ .../endpoint/tests/endpoint-import/README.md | 18 + .../tests/endpoint-update/00-assert.yaml | 26 ++ .../endpoint-update/00-minimal-resource.yaml | 28 ++ .../endpoint-update/00-prerequisites.yaml | 6 + .../tests/endpoint-update/00-secret.yaml | 6 + .../tests/endpoint-update/01-assert.yaml | 17 + .../endpoint-update/01-updated-resource.yaml | 10 + .../tests/endpoint-update/02-assert.yaml | 26 ++ .../endpoint-update/02-reverted-resource.yaml | 7 + .../endpoint/tests/endpoint-update/README.md | 17 + .../endpoint/zz_generated.adapter.go | 88 +++++ .../endpoint/zz_generated.controller.go | 45 +++ internal/osclients/endpoint.go | 104 ++++++ internal/osclients/mock/endpoint.go | 131 +++++++ .../api/v1alpha1/endpoint.go | 281 +++++++++++++++ .../api/v1alpha1/endpointfilter.go | 61 ++++ .../api/v1alpha1/endpointimport.go | 48 +++ .../api/v1alpha1/endpointresourcespec.go | 79 +++++ .../api/v1alpha1/endpointresourcestatus.go | 75 ++++ .../api/v1alpha1/endpointspec.go | 79 +++++ .../api/v1alpha1/endpointstatus.go | 66 ++++ .../clientset/typed/api/v1alpha1/endpoint.go | 74 ++++ .../typed/api/v1alpha1/fake/fake_endpoint.go | 51 +++ .../externalversions/api/v1alpha1/endpoint.go | 102 ++++++ pkg/clients/listers/api/v1alpha1/endpoint.go | 70 ++++ 78 files changed, 3562 insertions(+) create mode 100644 api/v1alpha1/endpoint_types.go create mode 100644 api/v1alpha1/zz_generated.endpoint-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_endpoints.yaml create mode 100644 config/samples/openstack_v1alpha1_endpoint.yaml create mode 100644 internal/controllers/endpoint/actuator.go create mode 100644 internal/controllers/endpoint/actuator_test.go create mode 100644 internal/controllers/endpoint/controller.go create mode 100644 internal/controllers/endpoint/status.go create mode 100644 internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-full/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-full/README.md create mode 100644 internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-minimal/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-minimal/01-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-create-minimal/README.md create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/01-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/02-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/03-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-dependency/README.md create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/01-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/02-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/03-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/04-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-dependency/README.md create mode 100644 internal/controllers/endpoint/tests/endpoint-import-error/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-error/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-error/01-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import-error/README.md create mode 100644 internal/controllers/endpoint/tests/endpoint-import/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-import/README.md create mode 100644 internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/00-secret.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/01-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/02-reverted-resource.yaml create mode 100644 internal/controllers/endpoint/tests/endpoint-update/README.md create mode 100644 internal/controllers/endpoint/zz_generated.adapter.go create mode 100644 internal/controllers/endpoint/zz_generated.controller.go create mode 100644 internal/osclients/endpoint.go create mode 100644 internal/osclients/mock/endpoint.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go create mode 100644 pkg/clients/listers/api/v1alpha1/endpoint.go diff --git a/api/v1alpha1/endpoint_types.go b/api/v1alpha1/endpoint_types.go new file mode 100644 index 000000000..62854ff0c --- /dev/null +++ b/api/v1alpha1/endpoint_types.go @@ -0,0 +1,88 @@ +/* +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 + +// EndpointResourceSpec contains the desired state of the resource. +type EndpointResourceSpec 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"` + + // serviceRef is a reference to the ORC Service which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serviceRef is immutable" + ServiceRef KubernetesNameRef `json:"serviceRef,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/identity/v3/endpoints + // + // 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"` +} + +// EndpointFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type EndpointFilter 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"` + + // serviceRef is a reference to the ORC Service which this resource is associated with. + // +optional + ServiceRef *KubernetesNameRef `json:"serviceRef,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/identity/v3/endpoints +} + +// EndpointResourceStatus represents the observed state of the resource. +type EndpointResourceStatus 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"` + + // serviceID is the ID of the Service to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ServiceID string `json:"serviceID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the Endpoint structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints +} diff --git a/api/v1alpha1/zz_generated.endpoint-resource.go b/api/v1alpha1/zz_generated.endpoint-resource.go new file mode 100644 index 000000000..33bebc76d --- /dev/null +++ b/api/v1alpha1/zz_generated.endpoint-resource.go @@ -0,0 +1,177 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EndpointImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type EndpointImport struct { + // id contains the unique identifier of an existing OpenStack resource. Note + // that when specifying an import by ID, the resource MUST already exist. + // The ORC object will enter an error state if the resource does not exist. + // +optional + // +kubebuilder:validation:Format:=uuid + ID *string `json:"id,omitempty"` + + // filter contains a resource query which is expected to return a single + // result. The controller will continue to retry if filter returns no + // results. If filter returns multiple results the controller will set an + // error state and will not continue to retry. + // +optional + Filter *EndpointFilter `json:"filter,omitempty"` +} + +// EndpointSpec 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 EndpointSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *EndpointImport `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 *EndpointResourceSpec `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"` +} + +// EndpointStatus defines the observed state of an ORC resource. +type EndpointStatus struct { + // conditions represents the observed status of the object. + // Known .status.conditions.type are: "Available", "Progressing" + // + // Available represents the availability of the OpenStack resource. If it is + // true then the resource is ready for use. + // + // Progressing indicates whether the controller is still attempting to + // reconcile the current state of the OpenStack resource to the desired + // state. Progressing will be False either because the desired state has + // been achieved, or because some terminal error prevents it from ever being + // achieved and the controller is no longer attempting to reconcile. If + // Progressing is True, an observer waiting on the resource should continue + // to wait. + // + // +kubebuilder:validation:MaxItems:=32 + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // id is the unique identifier of the OpenStack resource. + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *EndpointResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &Endpoint{} + +func (i *Endpoint) 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" + +// Endpoint is the Schema for an ORC resource. +type Endpoint struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +optional + Spec EndpointSpec `json:"spec,omitempty"` + + // status defines the observed state of the resource. + // +optional + Status EndpointStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// EndpointList contains a list of Endpoint. +type EndpointList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of Endpoint. + // +required + Items []Endpoint `json:"items"` +} + +func (l *EndpointList) GetItems() []Endpoint { + return l.Items +} + +func init() { + SchemeBuilder.Register(&Endpoint{}, &EndpointList{}) +} + +func (i *Endpoint) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &Endpoint{} diff --git a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml new file mode 100644 index 000000000..34a49522b --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml @@ -0,0 +1,322 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: endpoints.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: Endpoint + listKind: EndpointList + plural: endpoints + singular: endpoint + 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: Endpoint 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: + interface: + description: interface of the existing endpoint. + type: string + serviceRef: + description: serviceRef is a reference to which the endpoint + belongs. + maxLength: 253 + minLength: 1 + type: string + url: + description: url is the URL of the existing endpoint. + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + enabled: + default: true + description: enabled indicates whether the endpoint is enabled + or not. + type: boolean + interface: + description: interface indicates the visibility of the endpoint. + enum: + - admin + - internal + - public + 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 + serviceRef: + description: serviceRef is a reference to the ORC Service which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: serviceRef is immutable + rule: self == oldSelf + url: + description: url is the endpoint URL. + type: string + required: + - interface + - serviceRef + - url + type: object + required: + - cloudCredentialsRef + type: object + x-kubernetes-validations: + - message: resource must be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true' + - message: import may not be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__) + : true' + - message: resource may not be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource) + : true' + - message: import must be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__) + : true' + - message: managedOptions may only be provided when policy is managed + rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed'' + : true' + status: + description: status defines the observed state of the resource. + properties: + conditions: + description: |- + conditions represents the observed status of the object. + Known .status.conditions.type are: "Available", "Progressing" + + Available represents the availability of the OpenStack resource. If it is + true then the resource is ready for use. + + Progressing indicates whether the controller is still attempting to + reconcile the current state of the OpenStack resource to the desired + state. Progressing will be False either because the desired state has + been achieved, or because some terminal error prevents it from ever being + achieved and the controller is no longer attempting to reconcile. If + Progressing is True, an observer waiting on the resource should continue + to wait. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + id: + description: id is the unique identifier of the OpenStack resource. + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + enabled: + description: enabled indicates whether the endpoint is enabled + or not. + type: boolean + interface: + description: interface indicates the visibility of the endpoint. + enum: + - admin + - internal + - public + type: string + name: + description: name is a Human-readable name for the resource. Might + not be unique. + maxLength: 1024 + type: string + serviceID: + description: serviceID is the ID of the Service to which the resource + is associated. + maxLength: 1024 + type: string + url: + description: url is the endpoint URL. + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/samples/openstack_v1alpha1_endpoint.yaml b/config/samples/openstack_v1alpha1_endpoint.yaml new file mode 100644 index 000000000..22dcf1591 --- /dev/null +++ b/config/samples/openstack_v1alpha1_endpoint.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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 Endpoint + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/endpoint/actuator.go b/internal/controllers/endpoint/actuator.go new file mode 100644 index 000000000..13d01ebce --- /dev/null +++ b/internal/controllers/endpoint/actuator.go @@ -0,0 +1,271 @@ +/* +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 endpoint + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints" + 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 = endpoints.Endpoint + + 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 endpointActuator struct { + osClient osclients.EndpointClient + k8sClient client.Client +} + +var _ createResourceActuator = endpointActuator{} +var _ deleteResourceActuator = endpointActuator{} + +func (endpointActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator endpointActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetEndpoint(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator endpointActuator) 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 := endpoints.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListEndpoints(ctx, listOpts), true +} + +func (actuator endpointActuator) 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 + + service, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.ServiceRef, "Service", + func(dep *orcv1alpha1.Service) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := endpoints.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + ServiceID: ptr.Deref(service.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListEndpoints(ctx, listOpts), reconcileStatus +} + +func (actuator endpointActuator) 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 serviceID string + service, serviceDepRS := serviceDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(serviceDepRS) + if service != nil { + serviceID = ptr.Deref(service.Status.ID, "") + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := endpoints.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + ServiceID: serviceID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateEndpoint(ctx, createOpts) + if err != nil { + // We should require the spec to be updated before retrying a create which returned a conflict + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + return osResource, nil +} + +func (actuator endpointActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteEndpoint(ctx, resource.ID)) +} + +func (actuator endpointActuator) 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 := endpoints.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.UpdateEndpoint(ctx, osResource.ID, updateOpts) + + // We should require the spec to be updated before retrying an update which returned a conflict + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts endpoints.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToEndpointUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["endpoint"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *endpoints.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *endpoints.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator endpointActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type endpointHelperFactory struct{} + +var _ helperFactory = endpointHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.Endpoint, controller interfaces.ResourceController) (endpointActuator, 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 endpointActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return endpointActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewEndpointClient() + if err != nil { + return endpointActuator{}, progress.WrapError(err) + } + + return endpointActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (endpointHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return endpointAdapter{obj} +} + +func (endpointHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (endpointHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/endpoint/actuator_test.go b/internal/controllers/endpoint/actuator_test.go new file mode 100644 index 000000000..3d3f16b4e --- /dev/null +++ b/internal/controllers/endpoint/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 endpoint + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints" + 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 endpoints.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: endpoints.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: endpoints.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.Endpoint{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.EndpointSpec{ + Resource: &orcv1alpha1.EndpointResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := endpoints.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.EndpointResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := endpoints.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/endpoint/controller.go b/internal/controllers/endpoint/controller.go new file mode 100644 index 000000000..f1939076e --- /dev/null +++ b/internal/controllers/endpoint/controller.go @@ -0,0 +1,114 @@ +/* +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 endpoint + +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 = "endpoint" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=endpoints,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=endpoints/status,verbs=get;update;patch + +type endpointReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return endpointReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (endpointReconcilerConstructor) GetName() string { + return controllerName +} + +var serviceDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.EndpointList, *orcv1alpha1.Service]( + "spec.resource.serviceRef", + func(endpoint *orcv1alpha1.Endpoint) []string { + resource := endpoint.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.ServiceRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var serviceImportDependency = dependency.NewDependency[*orcv1alpha1.EndpointList, *orcv1alpha1.Service]( + "spec.import.filter.serviceRef", + func(endpoint *orcv1alpha1.Endpoint) []string { + resource := endpoint.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ServiceRef == nil { + return nil + } + return []string{string(*resource.Filter.ServiceRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c endpointReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + serviceWatchEventHandler, err := serviceDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + serviceImportWatchEventHandler, err := serviceImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Service{}, serviceWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Service{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Service{}, serviceImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Service{})), + ). + For(&orcv1alpha1.Endpoint{}) + + if err := errors.Join( + serviceDependency.AddToManager(ctx, mgr), + serviceImportDependency.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, endpointHelperFactory{}, endpointStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/endpoint/status.go b/internal/controllers/endpoint/status.go new file mode 100644 index 000000000..86b2031f5 --- /dev/null +++ b/internal/controllers/endpoint/status.go @@ -0,0 +1,64 @@ +/* +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 endpoint + +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 endpointStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.EndpointApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.EndpointStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.Endpoint, *osResourceT, *objectApplyT, *statusApplyT] = endpointStatusWriter{} + +func (endpointStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.Endpoint(name, namespace) +} + +func (endpointStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Endpoint, 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 (endpointStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.EndpointResourceStatus(). + WithServiceID(osResource.ServiceID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the EndpointResourceStatus 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/endpoint/tests/endpoint-create-full/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml new file mode 100644 index 000000000..881c9a9f4 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-create-full +status: + resource: + name: endpoint-create-full-override + description: Endpoint 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: Endpoint + name: endpoint-create-full + ref: endpoint + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-create-full + ref: service +assertAll: + - celExpr: "endpoint.status.id != ''" + - celExpr: "endpoint.status.resource.serviceID == service.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml new file mode 100644 index 000000000..59e2202de --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-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: Endpoint +metadata: + name: endpoint-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: endpoint-create-full-override + description: Endpoint from "create full" test + serviceRef: endpoint-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/endpoint/tests/endpoint-create-full/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-create-full/README.md b/internal/controllers/endpoint/tests/endpoint-create-full/README.md new file mode 100644 index 000000000..d625e7b61 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-create-full/README.md @@ -0,0 +1,11 @@ +# Create a Endpoint with all the options + +## Step 00 + +Create a Endpoint 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/endpoint/tests/endpoint-create-minimal/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml new file mode 100644 index 000000000..76d25598b --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-create-minimal +status: + resource: + name: endpoint-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: Endpoint + name: endpoint-create-minimal + ref: endpoint + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-create-minimal + ref: service +assertAll: + - celExpr: "endpoint.status.id != ''" + - celExpr: "endpoint.status.resource.serviceID == service.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..54dd56712 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-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): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: + serviceRef: endpoint-create-minimal diff --git a/internal/controllers/endpoint/tests/endpoint-create-minimal/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-create-minimal/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/01-assert.yaml new file mode 100644 index 000000000..e724dcbaa --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint' in secret.metadata.finalizers" diff --git a/internal/controllers/endpoint/tests/endpoint-create-minimal/01-delete-secret.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-create-minimal/README.md b/internal/controllers/endpoint/tests/endpoint-create-minimal/README.md new file mode 100644 index 000000000..b75955791 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a Endpoint with the minimum options + +## Step 00 + +Create a minimal Endpoint, 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/endpoint/tests/endpoint-dependency/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/00-assert.yaml new file mode 100644 index 000000000..d38283ca6 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/endpoint-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/endpoint-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-dependency-no-service +status: + conditions: + - type: Available + message: Waiting for Service/endpoint-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Service/endpoint-dependency-pending to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..ab2e0bd3a --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-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: Endpoint +metadata: + name: endpoint-dependency-no-service +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: endpoint-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource + +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: endpoint-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + serviceRef: endpoint-dependency diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-dependency/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/01-assert.yaml new file mode 100644 index 000000000..c0c5097dd --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/01-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: Endpoint +metadata: + name: endpoint-dependency-no-service +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/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..4d89ceeb0 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic endpoint-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-dependency-pending +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/endpoint/tests/endpoint-dependency/02-assert.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/02-assert.yaml new file mode 100644 index 000000000..8dbeb3371 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/02-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-dependency + ref: service + - apiVersion: v1 + kind: Secret + name: endpoint-dependency + ref: secret +assertAll: + - celExpr: "service.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/endpoint' in service.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/endpoint' in secret.metadata.finalizers" diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/02-delete-dependencies.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..67c2751dd --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/02-delete-dependencies.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete service.openstack.k-orc.cloud endpoint-dependency --wait=false + namespaced: true + - command: kubectl delete secret endpoint-dependency --wait=false + namespaced: true diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/03-assert.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/03-assert.yaml new file mode 100644 index 000000000..6526468ad --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/03-assert.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get service.openstack.k-orc.cloud endpoint-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret endpoint-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/03-delete-resources.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..89be93a7b --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/03-delete-resources.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-dependency-no-service diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/README.md b/internal/controllers/endpoint/tests/endpoint-dependency/README.md new file mode 100644 index 000000000..34cbddcda --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create Endpoints referencing non-existing resources. Each Endpoint is dependent on other non-existing resource. Verify that the Endpoints are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the Endpoints 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 Endpoints and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-assert.yaml new file mode 100644 index 000000000..17743d6e9 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Service/endpoint-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Service/endpoint-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..6a5c8737b --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: endpoint-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + serviceRef: endpoint-import-dependency diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-import-dependency/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/01-assert.yaml new file mode 100644 index 000000000..438dd019a --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: Endpoint +metadata: + name: endpoint-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Service/endpoint-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Service/endpoint-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..55f8cede6 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-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 `endpoint-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: + serviceRef: endpoint-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/02-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/02-assert.yaml new file mode 100644 index 000000000..76b42d4b6 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/02-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-import-dependency + ref: endpoint1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-import-dependency-not-this-one + ref: endpoint2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-import-dependency + ref: service +assertAll: + - celExpr: "endpoint1.status.id != endpoint2.status.id" + - celExpr: "endpoint1.status.resource.serviceID == service.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..55de8276b --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-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: Endpoint +metadata: + name: endpoint-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + serviceRef: endpoint-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/03-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/03-assert.yaml new file mode 100644 index 000000000..3d5d36d5e --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/03-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get service.openstack.k-orc.cloud endpoint-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..df9a4359c --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete service.openstack.k-orc.cloud endpoint-import-dependency + namespaced: true diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/04-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/04-assert.yaml new file mode 100644 index 000000000..108f72ce3 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get endpoint.openstack.k-orc.cloud endpoint-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/04-delete-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..56a973e5b --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-import-dependency diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md b/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md new file mode 100644 index 000000000..934ab20fe --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported Endpoint + +## Step 00 + +Import a Endpoint that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the Endpoint is waiting for the dependency to be ready. + +## Step 01 + +Create a Endpoint matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a Endpoint matching the import filters. + +Verify that the observed status on the imported Endpoint corresponds to the spec of the created Endpoint. + +## 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 Endpoint and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/endpoint/tests/endpoint-import-error/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/00-assert.yaml new file mode 100644 index 000000000..06992d40e --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: Endpoint +metadata: + name: endpoint-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/endpoint/tests/endpoint-import-error/00-create-resources.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml new file mode 100644 index 000000000..5c513bef5 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-import-error +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: Endpoint +metadata: + name: endpoint-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: Endpoint from "import error" test + serviceRef: endpoint-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: Endpoint from "import error" test + serviceRef: endpoint-import-error + # TODO(scaffolding): add any required field diff --git a/internal/controllers/endpoint/tests/endpoint-import-error/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-import-error/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/01-assert.yaml new file mode 100644 index 000000000..e9751d908 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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/endpoint/tests/endpoint-import-error/01-import-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml new file mode 100644 index 000000000..df0e2d3a9 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: Endpoint from "import error" test diff --git a/internal/controllers/endpoint/tests/endpoint-import-error/README.md b/internal/controllers/endpoint/tests/endpoint-import-error/README.md new file mode 100644 index 000000000..7a8e80bcf --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import-error/README.md @@ -0,0 +1,13 @@ +# Import Endpoint with more than one matching resources + +## Step 00 + +Create two Endpoints with identical specs. + +## Step 01 + +Ensure that an imported Endpoint with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/endpoint/tests/endpoint-import/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import/00-assert.yaml new file mode 100644 index 000000000..cc87b9a8e --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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/endpoint/tests/endpoint-import/00-import-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml new file mode 100644 index 000000000..cdfda600d --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: endpoint-import-external + description: Endpoint endpoint-import-external from "endpoint-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/endpoint/tests/endpoint-import/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-import/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml new file mode 100644 index 000000000..63a67bdbd --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: endpoint-import-external-not-this-one + description: Endpoint endpoint-import-external from "endpoint-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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/endpoint/tests/endpoint-import/01-create-trap-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..21a09ab23 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-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 + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `endpoint-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: Endpoint +metadata: + name: endpoint-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: Endpoint endpoint-import-external from "endpoint-import" test + serviceRef: endpoint-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml new file mode 100644 index 000000000..b3c7c0b04 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-import-external + ref: endpoint1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-import-external-not-this-one + ref: endpoint2 +assertAll: + - celExpr: "endpoint1.status.id != endpoint2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-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: endpoint-import-external + description: Endpoint endpoint-import-external from "endpoint-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml new file mode 100644 index 000000000..dcc1e86b8 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-import +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: Endpoint +metadata: + name: endpoint-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: Endpoint endpoint-import-external from "endpoint-import" test + serviceRef: endpoint-import + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/endpoint/tests/endpoint-import/README.md b/internal/controllers/endpoint/tests/endpoint-import/README.md new file mode 100644 index 000000000..d4caf4981 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-import/README.md @@ -0,0 +1,18 @@ +# Import Endpoint + +## Step 00 + +Import a endpoint that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a endpoint 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 endpoint matching the filter and verify that the observed status on the imported endpoint corresponds to the spec of the created endpoint. +Also, confirm that it does not adopt any endpoint whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml new file mode 100644 index 000000000..49cc482c0 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-update + ref: endpoint +assertAll: + - celExpr: "!has(endpoint.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-update +status: + resource: + name: endpoint-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/endpoint/tests/endpoint-update/00-minimal-resource.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml new file mode 100644 index 000000000..cdb8d1be4 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-update +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: Endpoint +metadata: + name: endpoint-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: + serviceRef: endpoint-update diff --git a/internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/endpoint/tests/endpoint-update/00-secret.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-update/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-update/01-assert.yaml new file mode 100644 index 000000000..e526907c7 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-update +status: + resource: + name: endpoint-update-updated + description: endpoint-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/endpoint/tests/endpoint-update/01-updated-resource.yaml b/internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml new file mode 100644 index 000000000..ea78d64af --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-update +spec: + resource: + name: endpoint-update-updated + description: endpoint-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml b/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml new file mode 100644 index 000000000..c3e8f879e --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-update + ref: endpoint +assertAll: + - celExpr: "!has(endpoint.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Endpoint +metadata: + name: endpoint-update +status: + resource: + name: endpoint-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/endpoint/tests/endpoint-update/02-reverted-resource.yaml b/internal/controllers/endpoint/tests/endpoint-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-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/endpoint/tests/endpoint-update/README.md b/internal/controllers/endpoint/tests/endpoint-update/README.md new file mode 100644 index 000000000..393d7fc78 --- /dev/null +++ b/internal/controllers/endpoint/tests/endpoint-update/README.md @@ -0,0 +1,17 @@ +# Update Endpoint + +## Step 00 + +Create a Endpoint using only mandatory fields. + +## Step 01 + +Update all mutable fields. + +## Step 02 + +Revert the resource to its original value and verify that the resulting object matches its state when first created. + +## Reference + +https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/controllers/endpoint/zz_generated.adapter.go b/internal/controllers/endpoint/zz_generated.adapter.go new file mode 100644 index 000000000..934d9b7da --- /dev/null +++ b/internal/controllers/endpoint/zz_generated.adapter.go @@ -0,0 +1,88 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package endpoint + +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.Endpoint + orcObjectListT = orcv1alpha1.EndpointList + resourceSpecT = orcv1alpha1.EndpointResourceSpec + filterT = orcv1alpha1.EndpointFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = endpointAdapter +) + +type endpointAdapter struct { + *orcv1alpha1.Endpoint +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.Endpoint +} + +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/endpoint/zz_generated.controller.go b/internal/controllers/endpoint/zz_generated.controller.go new file mode 100644 index 000000000..1dd55a109 --- /dev/null +++ b/internal/controllers/endpoint/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package endpoint + +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/endpoint.go b/internal/osclients/endpoint.go new file mode 100644 index 000000000..df0c7c1f9 --- /dev/null +++ b/internal/osclients/endpoint.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/identity/v3/endpoints" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type EndpointClient interface { + ListEndpoints(ctx context.Context, listOpts endpoints.ListOptsBuilder) iter.Seq2[*endpoints.Endpoint, error] + CreateEndpoint(ctx context.Context, opts endpoints.CreateOptsBuilder) (*endpoints.Endpoint, error) + DeleteEndpoint(ctx context.Context, resourceID string) error + GetEndpoint(ctx context.Context, resourceID string) (*endpoints.Endpoint, error) + UpdateEndpoint(ctx context.Context, id string, opts endpoints.UpdateOptsBuilder) (*endpoints.Endpoint, error) +} + +type endpointClient struct{ client *gophercloud.ServiceClient } + +// NewEndpointClient returns a new OpenStack client. +func NewEndpointClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (EndpointClient, error) { + client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create endpoint service client: %v", err) + } + + return &endpointClient{client}, nil +} + +func (c endpointClient) ListEndpoints(ctx context.Context, listOpts endpoints.ListOptsBuilder) iter.Seq2[*endpoints.Endpoint, error] { + pager := endpoints.List(c.client, listOpts) + return func(yield func(*endpoints.Endpoint, error) bool) { + _ = pager.EachPage(ctx, yieldPage(endpoints.ExtractEndpoints, yield)) + } +} + +func (c endpointClient) CreateEndpoint(ctx context.Context, opts endpoints.CreateOptsBuilder) (*endpoints.Endpoint, error) { + return endpoints.Create(ctx, c.client, opts).Extract() +} + +func (c endpointClient) DeleteEndpoint(ctx context.Context, resourceID string) error { + return endpoints.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c endpointClient) GetEndpoint(ctx context.Context, resourceID string) (*endpoints.Endpoint, error) { + return endpoints.Get(ctx, c.client, resourceID).Extract() +} + +func (c endpointClient) UpdateEndpoint(ctx context.Context, id string, opts endpoints.UpdateOptsBuilder) (*endpoints.Endpoint, error) { + return endpoints.Update(ctx, c.client, id, opts).Extract() +} + +type endpointErrorClient struct{ error } + +// NewEndpointErrorClient returns a EndpointClient in which every method returns the given error. +func NewEndpointErrorClient(e error) EndpointClient { + return endpointErrorClient{e} +} + +func (e endpointErrorClient) ListEndpoints(_ context.Context, _ endpoints.ListOptsBuilder) iter.Seq2[*endpoints.Endpoint, error] { + return func(yield func(*endpoints.Endpoint, error) bool) { + yield(nil, e.error) + } +} + +func (e endpointErrorClient) CreateEndpoint(_ context.Context, _ endpoints.CreateOptsBuilder) (*endpoints.Endpoint, error) { + return nil, e.error +} + +func (e endpointErrorClient) DeleteEndpoint(_ context.Context, _ string) error { + return e.error +} + +func (e endpointErrorClient) GetEndpoint(_ context.Context, _ string) (*endpoints.Endpoint, error) { + return nil, e.error +} + +func (e endpointErrorClient) UpdateEndpoint(_ context.Context, _ string, _ endpoints.UpdateOptsBuilder) (*endpoints.Endpoint, error) { + return nil, e.error +} diff --git a/internal/osclients/mock/endpoint.go b/internal/osclients/mock/endpoint.go new file mode 100644 index 000000000..dafc92276 --- /dev/null +++ b/internal/osclients/mock/endpoint.go @@ -0,0 +1,131 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../endpoint.go +// +// Generated by this command: +// +// mockgen -package mock -destination=endpoint.go -source=../endpoint.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock EndpointClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + endpoints "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints" + gomock "go.uber.org/mock/gomock" +) + +// MockEndpointClient is a mock of EndpointClient interface. +type MockEndpointClient struct { + ctrl *gomock.Controller + recorder *MockEndpointClientMockRecorder + isgomock struct{} +} + +// MockEndpointClientMockRecorder is the mock recorder for MockEndpointClient. +type MockEndpointClientMockRecorder struct { + mock *MockEndpointClient +} + +// NewMockEndpointClient creates a new mock instance. +func NewMockEndpointClient(ctrl *gomock.Controller) *MockEndpointClient { + mock := &MockEndpointClient{ctrl: ctrl} + mock.recorder = &MockEndpointClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEndpointClient) EXPECT() *MockEndpointClientMockRecorder { + return m.recorder +} + +// CreateEndpoint mocks base method. +func (m *MockEndpointClient) CreateEndpoint(ctx context.Context, opts endpoints.CreateOptsBuilder) (*endpoints.Endpoint, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateEndpoint", ctx, opts) + ret0, _ := ret[0].(*endpoints.Endpoint) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateEndpoint indicates an expected call of CreateEndpoint. +func (mr *MockEndpointClientMockRecorder) CreateEndpoint(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).CreateEndpoint), ctx, opts) +} + +// DeleteEndpoint mocks base method. +func (m *MockEndpointClient) DeleteEndpoint(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteEndpoint", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteEndpoint indicates an expected call of DeleteEndpoint. +func (mr *MockEndpointClientMockRecorder) DeleteEndpoint(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).DeleteEndpoint), ctx, resourceID) +} + +// GetEndpoint mocks base method. +func (m *MockEndpointClient) GetEndpoint(ctx context.Context, resourceID string) (*endpoints.Endpoint, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEndpoint", ctx, resourceID) + ret0, _ := ret[0].(*endpoints.Endpoint) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEndpoint indicates an expected call of GetEndpoint. +func (mr *MockEndpointClientMockRecorder) GetEndpoint(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).GetEndpoint), ctx, resourceID) +} + +// ListEndpoints mocks base method. +func (m *MockEndpointClient) ListEndpoints(ctx context.Context, listOpts endpoints.ListOptsBuilder) iter.Seq2[*endpoints.Endpoint, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListEndpoints", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*endpoints.Endpoint, error]) + return ret0 +} + +// ListEndpoints indicates an expected call of ListEndpoints. +func (mr *MockEndpointClientMockRecorder) ListEndpoints(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListEndpoints", reflect.TypeOf((*MockEndpointClient)(nil).ListEndpoints), ctx, listOpts) +} + +// UpdateEndpoint mocks base method. +func (m *MockEndpointClient) UpdateEndpoint(ctx context.Context, id string, opts endpoints.UpdateOptsBuilder) (*endpoints.Endpoint, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateEndpoint", ctx, id, opts) + ret0, _ := ret[0].(*endpoints.Endpoint) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateEndpoint indicates an expected call of UpdateEndpoint. +func (mr *MockEndpointClientMockRecorder) UpdateEndpoint(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).UpdateEndpoint), ctx, id, opts) +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go new file mode 100644 index 000000000..a099f728f --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go @@ -0,0 +1,281 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + internal "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// EndpointApplyConfiguration represents a declarative configuration of the Endpoint type for use +// with apply. +type EndpointApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *EndpointSpecApplyConfiguration `json:"spec,omitempty"` + Status *EndpointStatusApplyConfiguration `json:"status,omitempty"` +} + +// Endpoint constructs a declarative configuration of the Endpoint type for use with +// apply. +func Endpoint(name, namespace string) *EndpointApplyConfiguration { + b := &EndpointApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Endpoint") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractEndpoint extracts the applied configuration owned by fieldManager from +// endpoint. If no managedFields are found in endpoint for fieldManager, a +// EndpointApplyConfiguration 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. +// endpoint must be a unmodified Endpoint API object that was retrieved from the Kubernetes API. +// ExtractEndpoint 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 ExtractEndpoint(endpoint *apiv1alpha1.Endpoint, fieldManager string) (*EndpointApplyConfiguration, error) { + return extractEndpoint(endpoint, fieldManager, "") +} + +// ExtractEndpointStatus is the same as ExtractEndpoint except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractEndpointStatus(endpoint *apiv1alpha1.Endpoint, fieldManager string) (*EndpointApplyConfiguration, error) { + return extractEndpoint(endpoint, fieldManager, "status") +} + +func extractEndpoint(endpoint *apiv1alpha1.Endpoint, fieldManager string, subresource string) (*EndpointApplyConfiguration, error) { + b := &EndpointApplyConfiguration{} + err := managedfields.ExtractInto(endpoint, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Endpoint"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(endpoint.Name) + b.WithNamespace(endpoint.Namespace) + + b.WithKind("Endpoint") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b EndpointApplyConfiguration) 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 *EndpointApplyConfiguration) WithKind(value string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithAPIVersion(value string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithName(value string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithGenerateName(value string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithNamespace(value string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithUID(value types.UID) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithResourceVersion(value string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithGeneration(value int64) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithCreationTimestamp(value metav1.Time) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithLabels(entries map[string]string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithAnnotations(entries map[string]string) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithFinalizers(values ...string) *EndpointApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *EndpointApplyConfiguration) 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 *EndpointApplyConfiguration) WithSpec(value *EndpointSpecApplyConfiguration) *EndpointApplyConfiguration { + 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 *EndpointApplyConfiguration) WithStatus(value *EndpointStatusApplyConfiguration) *EndpointApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *EndpointApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *EndpointApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *EndpointApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *EndpointApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go new file mode 100644 index 000000000..0908e2c55 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// EndpointFilterApplyConfiguration represents a declarative configuration of the EndpointFilter type for use +// with apply. +type EndpointFilterApplyConfiguration struct { + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` + Interface *string `json:"interface,omitempty"` + URL *string `json:"url,omitempty"` +} + +// EndpointFilterApplyConfiguration constructs a declarative configuration of the EndpointFilter type for use with +// apply. +func EndpointFilter() *EndpointFilterApplyConfiguration { + return &EndpointFilterApplyConfiguration{} +} + +// WithServiceRef sets the ServiceRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceRef field is set to the value of the last call. +func (b *EndpointFilterApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *EndpointFilterApplyConfiguration { + b.ServiceRef = &value + return b +} + +// WithInterface sets the Interface field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Interface field is set to the value of the last call. +func (b *EndpointFilterApplyConfiguration) WithInterface(value string) *EndpointFilterApplyConfiguration { + b.Interface = &value + return b +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *EndpointFilterApplyConfiguration) WithURL(value string) *EndpointFilterApplyConfiguration { + b.URL = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go new file mode 100644 index 000000000..e20a99cd7 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go @@ -0,0 +1,48 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// EndpointImportApplyConfiguration represents a declarative configuration of the EndpointImport type for use +// with apply. +type EndpointImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *EndpointFilterApplyConfiguration `json:"filter,omitempty"` +} + +// EndpointImportApplyConfiguration constructs a declarative configuration of the EndpointImport type for use with +// apply. +func EndpointImport() *EndpointImportApplyConfiguration { + return &EndpointImportApplyConfiguration{} +} + +// 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 *EndpointImportApplyConfiguration) WithID(value string) *EndpointImportApplyConfiguration { + 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 *EndpointImportApplyConfiguration) WithFilter(value *EndpointFilterApplyConfiguration) *EndpointImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go new file mode 100644 index 000000000..b9f157902 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go @@ -0,0 +1,79 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// EndpointResourceSpecApplyConfiguration represents a declarative configuration of the EndpointResourceSpec type for use +// with apply. +type EndpointResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Interface *string `json:"interface,omitempty"` + URL *string `json:"url,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` +} + +// EndpointResourceSpecApplyConfiguration constructs a declarative configuration of the EndpointResourceSpec type for use with +// apply. +func EndpointResourceSpec() *EndpointResourceSpecApplyConfiguration { + return &EndpointResourceSpecApplyConfiguration{} +} + +// 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 *EndpointResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *EndpointResourceSpecApplyConfiguration { + b.Name = &value + return b +} + +// WithEnabled sets the Enabled field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Enabled field is set to the value of the last call. +func (b *EndpointResourceSpecApplyConfiguration) WithEnabled(value bool) *EndpointResourceSpecApplyConfiguration { + b.Enabled = &value + return b +} + +// WithInterface sets the Interface field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Interface field is set to the value of the last call. +func (b *EndpointResourceSpecApplyConfiguration) WithInterface(value string) *EndpointResourceSpecApplyConfiguration { + b.Interface = &value + return b +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *EndpointResourceSpecApplyConfiguration) WithURL(value string) *EndpointResourceSpecApplyConfiguration { + b.URL = &value + return b +} + +// WithServiceRef sets the ServiceRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceRef field is set to the value of the last call. +func (b *EndpointResourceSpecApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *EndpointResourceSpecApplyConfiguration { + b.ServiceRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go new file mode 100644 index 000000000..8a0b7c87c --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go @@ -0,0 +1,75 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// EndpointResourceStatusApplyConfiguration represents a declarative configuration of the EndpointResourceStatus type for use +// with apply. +type EndpointResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Interface *string `json:"interface,omitempty"` + URL *string `json:"url,omitempty"` + ServiceID *string `json:"serviceID,omitempty"` +} + +// EndpointResourceStatusApplyConfiguration constructs a declarative configuration of the EndpointResourceStatus type for use with +// apply. +func EndpointResourceStatus() *EndpointResourceStatusApplyConfiguration { + return &EndpointResourceStatusApplyConfiguration{} +} + +// 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 *EndpointResourceStatusApplyConfiguration) WithName(value string) *EndpointResourceStatusApplyConfiguration { + b.Name = &value + return b +} + +// WithEnabled sets the Enabled field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Enabled field is set to the value of the last call. +func (b *EndpointResourceStatusApplyConfiguration) WithEnabled(value bool) *EndpointResourceStatusApplyConfiguration { + b.Enabled = &value + return b +} + +// WithInterface sets the Interface field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Interface field is set to the value of the last call. +func (b *EndpointResourceStatusApplyConfiguration) WithInterface(value string) *EndpointResourceStatusApplyConfiguration { + b.Interface = &value + return b +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *EndpointResourceStatusApplyConfiguration) WithURL(value string) *EndpointResourceStatusApplyConfiguration { + b.URL = &value + return b +} + +// WithServiceID sets the ServiceID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceID field is set to the value of the last call. +func (b *EndpointResourceStatusApplyConfiguration) WithServiceID(value string) *EndpointResourceStatusApplyConfiguration { + b.ServiceID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go new file mode 100644 index 000000000..fbe73d129 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go @@ -0,0 +1,79 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// EndpointSpecApplyConfiguration represents a declarative configuration of the EndpointSpec type for use +// with apply. +type EndpointSpecApplyConfiguration struct { + Import *EndpointImportApplyConfiguration `json:"import,omitempty"` + Resource *EndpointResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// EndpointSpecApplyConfiguration constructs a declarative configuration of the EndpointSpec type for use with +// apply. +func EndpointSpec() *EndpointSpecApplyConfiguration { + return &EndpointSpecApplyConfiguration{} +} + +// 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 *EndpointSpecApplyConfiguration) WithImport(value *EndpointImportApplyConfiguration) *EndpointSpecApplyConfiguration { + 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 *EndpointSpecApplyConfiguration) WithResource(value *EndpointResourceSpecApplyConfiguration) *EndpointSpecApplyConfiguration { + 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 *EndpointSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *EndpointSpecApplyConfiguration { + 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 *EndpointSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *EndpointSpecApplyConfiguration { + 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 *EndpointSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *EndpointSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go new file mode 100644 index 000000000..ab14837ef --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go @@ -0,0 +1,66 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// EndpointStatusApplyConfiguration represents a declarative configuration of the EndpointStatus type for use +// with apply. +type EndpointStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *EndpointResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// EndpointStatusApplyConfiguration constructs a declarative configuration of the EndpointStatus type for use with +// apply. +func EndpointStatus() *EndpointStatusApplyConfiguration { + return &EndpointStatusApplyConfiguration{} +} + +// 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 *EndpointStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *EndpointStatusApplyConfiguration { + 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 *EndpointStatusApplyConfiguration) WithID(value string) *EndpointStatusApplyConfiguration { + 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 *EndpointStatusApplyConfiguration) WithResource(value *EndpointResourceStatusApplyConfiguration) *EndpointStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go new file mode 100644 index 000000000..8c0f3c58b --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go @@ -0,0 +1,74 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigurationapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + scheme "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// EndpointsGetter has a method to return a EndpointInterface. +// A group's client should implement this interface. +type EndpointsGetter interface { + Endpoints(namespace string) EndpointInterface +} + +// EndpointInterface has methods to work with Endpoint resources. +type EndpointInterface interface { + Create(ctx context.Context, endpoint *apiv1alpha1.Endpoint, opts v1.CreateOptions) (*apiv1alpha1.Endpoint, error) + Update(ctx context.Context, endpoint *apiv1alpha1.Endpoint, opts v1.UpdateOptions) (*apiv1alpha1.Endpoint, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, endpoint *apiv1alpha1.Endpoint, opts v1.UpdateOptions) (*apiv1alpha1.Endpoint, 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.Endpoint, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.EndpointList, 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.Endpoint, err error) + Apply(ctx context.Context, endpoint *applyconfigurationapiv1alpha1.EndpointApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Endpoint, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, endpoint *applyconfigurationapiv1alpha1.EndpointApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.Endpoint, err error) + EndpointExpansion +} + +// endpoints implements EndpointInterface +type endpoints struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.Endpoint, *apiv1alpha1.EndpointList, *applyconfigurationapiv1alpha1.EndpointApplyConfiguration] +} + +// newEndpoints returns a Endpoints +func newEndpoints(c *OpenstackV1alpha1Client, namespace string) *endpoints { + return &endpoints{ + gentype.NewClientWithListAndApply[*apiv1alpha1.Endpoint, *apiv1alpha1.EndpointList, *applyconfigurationapiv1alpha1.EndpointApplyConfiguration]( + "endpoints", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.Endpoint { return &apiv1alpha1.Endpoint{} }, + func() *apiv1alpha1.EndpointList { return &apiv1alpha1.EndpointList{} }, + ), + } +} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go new file mode 100644 index 000000000..bc2842cde --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeEndpoints implements EndpointInterface +type fakeEndpoints struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.Endpoint, *v1alpha1.EndpointList, *apiv1alpha1.EndpointApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeEndpoints(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.EndpointInterface { + return &fakeEndpoints{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.Endpoint, *v1alpha1.EndpointList, *apiv1alpha1.EndpointApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("endpoints"), + v1alpha1.SchemeGroupVersion.WithKind("Endpoint"), + func() *v1alpha1.Endpoint { return &v1alpha1.Endpoint{} }, + func() *v1alpha1.EndpointList { return &v1alpha1.EndpointList{} }, + func(dst, src *v1alpha1.EndpointList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.EndpointList) []*v1alpha1.Endpoint { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.EndpointList, items []*v1alpha1.Endpoint) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go b/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go new file mode 100644 index 000000000..496b05405 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go @@ -0,0 +1,102 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v2apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + clientset "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset" + internalinterfaces "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/informers/externalversions/internalinterfaces" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/listers/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// EndpointInformer provides access to a shared informer and lister for +// Endpoints. +type EndpointInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.EndpointLister +} + +type endpointInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewEndpointInformer constructs a new informer for Endpoint 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 NewEndpointInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredEndpointInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredEndpointInformer constructs a new informer for Endpoint 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 NewFilteredEndpointInformer(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().Endpoints(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Endpoints(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Endpoints(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Endpoints(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.Endpoint{}, + resyncPeriod, + indexers, + ) +} + +func (f *endpointInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredEndpointInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *endpointInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.Endpoint{}, f.defaultInformer) +} + +func (f *endpointInformer) Lister() apiv1alpha1.EndpointLister { + return apiv1alpha1.NewEndpointLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/listers/api/v1alpha1/endpoint.go b/pkg/clients/listers/api/v1alpha1/endpoint.go new file mode 100644 index 000000000..427f09149 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/endpoint.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// EndpointLister helps list Endpoints. +// All objects returned here must be treated as read-only. +type EndpointLister interface { + // List lists all Endpoints in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Endpoint, err error) + // Endpoints returns an object that can list and get Endpoints. + Endpoints(namespace string) EndpointNamespaceLister + EndpointListerExpansion +} + +// endpointLister implements the EndpointLister interface. +type endpointLister struct { + listers.ResourceIndexer[*apiv1alpha1.Endpoint] +} + +// NewEndpointLister returns a new EndpointLister. +func NewEndpointLister(indexer cache.Indexer) EndpointLister { + return &endpointLister{listers.New[*apiv1alpha1.Endpoint](indexer, apiv1alpha1.Resource("endpoint"))} +} + +// Endpoints returns an object that can list and get Endpoints. +func (s *endpointLister) Endpoints(namespace string) EndpointNamespaceLister { + return endpointNamespaceLister{listers.NewNamespaced[*apiv1alpha1.Endpoint](s.ResourceIndexer, namespace)} +} + +// EndpointNamespaceLister helps list and get Endpoints. +// All objects returned here must be treated as read-only. +type EndpointNamespaceLister interface { + // List lists all Endpoints in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.Endpoint, err error) + // Get retrieves the Endpoint from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.Endpoint, error) + EndpointNamespaceListerExpansion +} + +// endpointNamespaceLister implements the EndpointNamespaceLister +// interface. +type endpointNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.Endpoint] +} From c1a0b5f8914c74a2e40fefb19084797a07678262 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 11 Feb 2026 21:53:16 +0000 Subject: [PATCH 052/121] keystone: endpoint controller implementation --- PROJECT | 8 + README.md | 2 +- api/v1alpha1/endpoint_types.go | 70 ++-- api/v1alpha1/zz_generated.deepcopy.go | 212 ++++++++++ .../zz_generated.endpoint-resource.go | 14 +- cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 370 ++++++++++++++++++ cmd/resource-generator/main.go | 4 + .../openstack.k-orc.cloud_endpoints.yaml | 49 ++- config/crd/kustomization.yaml | 1 + .../bases/orc.clusterserviceversion.yaml | 5 + config/rbac/role.yaml | 2 + config/samples/kustomization.yaml | 1 + .../samples/openstack_v1alpha1_endpoint.yaml | 8 +- internal/controllers/endpoint/actuator.go | 105 +++-- .../controllers/endpoint/actuator_test.go | 41 +- internal/controllers/endpoint/status.go | 7 +- .../tests/endpoint-create-full/00-assert.yaml | 9 +- .../00-create-resource.yaml | 17 +- .../endpoint-create-minimal/00-assert.yaml | 7 +- .../00-create-resource.yaml | 14 +- .../00-create-resources-missing-deps.yaml | 20 +- .../01-create-dependencies.yaml | 7 +- .../00-import-resource.yaml | 6 +- .../01-create-trap-resource.yaml | 13 +- .../02-create-resource.yaml | 11 +- .../00-create-resources.yaml | 21 +- .../01-import-resource.yaml | 5 +- .../endpoint-import/00-import-resource.yaml | 21 +- .../tests/endpoint-import/01-assert.yaml | 5 +- .../01-create-trap-resource.yaml | 14 +- .../tests/endpoint-import/02-assert.yaml | 10 +- .../endpoint-import/02-create-resource.yaml | 21 +- .../tests/endpoint-update/00-assert.yaml | 31 +- .../endpoint-update/00-minimal-resource.yaml | 17 +- .../endpoint-update/00-prerequisites.yaml | 6 - .../tests/endpoint-update/01-assert.yaml | 6 +- .../endpoint-update/01-updated-resource.yaml | 6 +- .../tests/endpoint-update/02-assert.yaml | 11 +- .../endpoint/zz_generated.adapter.go | 12 +- .../endpoint/zz_generated.controller.go | 2 +- .../port/tests/port-update/00-assert.yaml | 20 +- .../port/tests/port-update/01-assert.yaml | 2 +- internal/osclients/mock/doc.go | 3 + internal/osclients/mock/endpoint.go | 2 +- internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/endpoint.go | 2 +- .../api/v1alpha1/endpointfilter.go | 20 +- .../api/v1alpha1/endpointimport.go | 2 +- .../api/v1alpha1/endpointresourcespec.go | 20 +- .../api/v1alpha1/endpointresourcestatus.go | 20 +- .../api/v1alpha1/endpointspec.go | 2 +- .../api/v1alpha1/endpointstatus.go | 2 +- .../applyconfiguration/internal/internal.go | 115 ++++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../clientset/typed/api/v1alpha1/endpoint.go | 2 +- .../api/v1alpha1/fake/fake_api_client.go | 4 + .../typed/api/v1alpha1/fake/fake_endpoint.go | 2 +- .../typed/api/v1alpha1/generated_expansion.go | 2 + .../externalversions/api/v1alpha1/endpoint.go | 2 +- .../api/v1alpha1/interface.go | 7 + .../informers/externalversions/generic.go | 2 + pkg/clients/listers/api/v1alpha1/endpoint.go | 2 +- .../api/v1alpha1/expansion_generated.go | 8 + website/docs/crd-reference.md | 142 +++++++ 69 files changed, 1278 insertions(+), 330 deletions(-) delete mode 100644 internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml diff --git a/PROJECT b/PROJECT index 3d09e3c30..54fc55950 100644 --- a/PROJECT +++ b/PROJECT @@ -16,6 +16,14 @@ resources: kind: Domain path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: Endpoint + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/README.md b/README.md index 63d59ba73..c05143838 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ kubectl delete -f $ORC_RELEASE | **controller** | **1.x** | **2.x** | **main** | |:---------------------------:|:-------:|:-------:|:--------:| | domain | | ✔ | ✔ | +| endpoint | | ◐ | ◐ | | flavor | | ✔ | ✔ | | floating ip | | ◐ | ◐ | | group | | ✔ | ✔ | @@ -91,7 +92,6 @@ kubectl delete -f $ORC_RELEASE | volume type | | ◐ | ◐ | - ✔: mostly implemented ◐: partially implemented diff --git a/api/v1alpha1/endpoint_types.go b/api/v1alpha1/endpoint_types.go index 62854ff0c..fc2e9c5cc 100644 --- a/api/v1alpha1/endpoint_types.go +++ b/api/v1alpha1/endpoint_types.go @@ -18,71 +18,75 @@ package v1alpha1 // EndpointResourceSpec contains the desired state of the resource. type EndpointResourceSpec 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 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="description is immutable" Description *string `json:"description,omitempty"` + // enabled indicates whether the endpoint is enabled or not. + // +optional + Enabled *bool `json:"enabled,omitempty"` + + // interface indicates the visibility of the endpoint. + // +kubebuilder:validation:Enum:=admin;internal;public + // +required + Interface string `json:"interface,omitempty"` + + // url is the endpoint URL. + // +kubebuilder:validation:MaxLength=1024 + // +required + URL string `json:"url"` + // serviceRef is a reference to the ORC Service which this resource is associated with. // +required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serviceRef is immutable" ServiceRef KubernetesNameRef `json:"serviceRef,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/identity/v3/endpoints - // - // 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"` } // EndpointFilter defines an existing resource by its properties // +kubebuilder:validation:MinProperties:=1 type EndpointFilter struct { - // name of the existing resource + // interface of the existing endpoint. + // +kubebuilder:validation:Enum:=admin;internal;public // +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"` + Interface string `json:"interface,omitempty"` // serviceRef is a reference to the ORC Service which this resource is associated with. // +optional ServiceRef *KubernetesNameRef `json:"serviceRef,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/identity/v3/endpoints + // url is the URL of the existing endpoint. + // +kubebuilder:validation:MaxLength=1024 + // +optional + URL string `json:"url,omitempty"` } // EndpointResourceStatus represents the observed state of the resource. type EndpointResourceStatus struct { - // name is a Human-readable name for the resource. Might not be unique. - // +kubebuilder:validation:MaxLength=1024 + // description is a human-readable description for the resource. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description string `json:"description,omitempty"` + + // enabled indicates whether the endpoint is enabled or not. // +optional - Name string `json:"name,omitempty"` + Enabled *bool `json:"enabled,omitempty"` - // description is a human-readable description for the resource. + // interface indicates the visibility of the endpoint. + // +kubebuilder:validation:MaxLength=128 + // +optional + Interface string `json:"interface,omitempty"` + + // url is the endpoint URL. // +kubebuilder:validation:MaxLength=1024 // +optional - Description string `json:"description,omitempty"` + URL string `json:"url,omitempty"` // serviceID is the ID of the Service to which the resource is associated. // +kubebuilder:validation:MaxLength=1024 // +optional ServiceID string `json:"serviceID,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the Endpoint structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ef1494716..3f9a9f21f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -347,6 +347,218 @@ func (in *DomainStatus) DeepCopy() *DomainStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Endpoint) DeepCopyInto(out *Endpoint) { + *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 Endpoint. +func (in *Endpoint) DeepCopy() *Endpoint { + if in == nil { + return nil + } + out := new(Endpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Endpoint) 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 *EndpointFilter) DeepCopyInto(out *EndpointFilter) { + *out = *in + if in.ServiceRef != nil { + in, out := &in.ServiceRef, &out.ServiceRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointFilter. +func (in *EndpointFilter) DeepCopy() *EndpointFilter { + if in == nil { + return nil + } + out := new(EndpointFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointImport) DeepCopyInto(out *EndpointImport) { + *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(EndpointFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointImport. +func (in *EndpointImport) DeepCopy() *EndpointImport { + if in == nil { + return nil + } + out := new(EndpointImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointList) DeepCopyInto(out *EndpointList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Endpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointList. +func (in *EndpointList) DeepCopy() *EndpointList { + if in == nil { + return nil + } + out := new(EndpointList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EndpointList) 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 *EndpointResourceSpec) DeepCopyInto(out *EndpointResourceSpec) { + *out = *in + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointResourceSpec. +func (in *EndpointResourceSpec) DeepCopy() *EndpointResourceSpec { + if in == nil { + return nil + } + out := new(EndpointResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointResourceStatus) DeepCopyInto(out *EndpointResourceStatus) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointResourceStatus. +func (in *EndpointResourceStatus) DeepCopy() *EndpointResourceStatus { + if in == nil { + return nil + } + out := new(EndpointResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointSpec) DeepCopyInto(out *EndpointSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(EndpointImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(EndpointResourceSpec) + (*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 EndpointSpec. +func (in *EndpointSpec) DeepCopy() *EndpointSpec { + if in == nil { + return nil + } + out := new(EndpointSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointStatus) DeepCopyInto(out *EndpointStatus) { + *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(EndpointResourceStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointStatus. +func (in *EndpointStatus) DeepCopy() *EndpointStatus { + if in == nil { + return nil + } + out := new(EndpointStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalGateway) DeepCopyInto(out *ExternalGateway) { *out = *in diff --git a/api/v1alpha1/zz_generated.endpoint-resource.go b/api/v1alpha1/zz_generated.endpoint-resource.go index 33bebc76d..0fcc28d2d 100644 --- a/api/v1alpha1/zz_generated.endpoint-resource.go +++ b/api/v1alpha1/zz_generated.endpoint-resource.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. @@ -29,9 +29,10 @@ type EndpointImport struct { // id contains the unique identifier of an existing OpenStack resource. Note // that when specifying an import by ID, the resource MUST already exist. // The ORC object will enter an error state if the resource does not exist. - // +optional // +kubebuilder:validation:Format:=uuid - ID *string `json:"id,omitempty"` + // +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 @@ -76,7 +77,7 @@ type EndpointSpec struct { // cloudCredentialsRef points to a secret containing OpenStack credentials // +required - CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef"` + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` } // EndpointStatus defines the observed state of an ORC resource. @@ -104,6 +105,7 @@ type EndpointStatus struct { 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"` @@ -135,8 +137,8 @@ type Endpoint struct { metav1.ObjectMeta `json:"metadata,omitempty"` // spec specifies the desired state of the resource. - // +optional - Spec EndpointSpec `json:"spec,omitempty"` + // +required + Spec EndpointSpec `json:"spec,omitzero"` // status defines the observed state of the resource. // +optional diff --git a/cmd/manager/main.go b/cmd/manager/main.go index bf9bc38ca..bb5b27c69 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -28,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/domain" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/endpoint" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/flavor" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/floatingip" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" @@ -108,6 +109,7 @@ func main() { scopeFactory := scope.NewFactory(orcOpts.ScopeCacheMaxSize, caCerts) controllers := []interfaces.Controller{ + endpoint.New(scopeFactory), image.New(scopeFactory), network.New(scopeFactory), subnet.New(scopeFactory), diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index f47211048..d00ee16e4 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -44,6 +44,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_DomainResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainSpec": schema_openstack_resource_controller_v2_api_v1alpha1_DomainSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainStatus": schema_openstack_resource_controller_v2_api_v1alpha1_DomainStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Endpoint": schema_openstack_resource_controller_v2_api_v1alpha1_Endpoint(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointFilter": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointImport": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointList": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointSpec": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointStatus": schema_openstack_resource_controller_v2_api_v1alpha1_EndpointStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ExternalGateway": schema_openstack_resource_controller_v2_api_v1alpha1_ExternalGateway(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ExternalGatewayStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ExternalGatewayStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByKeystoneTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByKeystoneTags(ref), @@ -1012,6 +1020,368 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_DomainStatus(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_Endpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Endpoint 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.EndpointSpec"), + }, + }, + "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.EndpointStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointFilter defines an existing resource by its properties", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "interface": { + SchemaProps: spec.SchemaProps{ + Description: "interface of the existing endpoint.", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceRef": { + SchemaProps: spec.SchemaProps{ + Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "url is the URL of the existing endpoint.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointImport 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.EndpointFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointList contains a list of Endpoint.", + 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 Endpoint.", + 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.Endpoint"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Endpoint", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointResourceSpec contains the desired state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "enabled indicates whether the endpoint is enabled or not.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "interface": { + SchemaProps: spec.SchemaProps{ + Description: "interface indicates the visibility of the endpoint.", + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "url is the endpoint URL.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceRef": { + SchemaProps: spec.SchemaProps{ + Description: "serviceRef is a reference to the ORC Service which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"interface", "url", "serviceRef"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointResourceStatus represents the observed state of the resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human-readable description for the resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "enabled indicates whether the endpoint is enabled or not.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "interface": { + SchemaProps: spec.SchemaProps{ + Description: "interface indicates the visibility of the endpoint.", + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "url is the endpoint URL.", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceID": { + SchemaProps: spec.SchemaProps{ + Description: "serviceID is the ID of the Service to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointSpec 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.EndpointImport"), + }, + }, + "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.EndpointResourceSpec"), + }, + }, + "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.EndpointImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointStatus 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.EndpointResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ExternalGateway(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 45e9d364b..0e7444cc1 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -164,6 +164,10 @@ var resources []templateFields = []templateFields{ { Name: "Group", }, + { + Name: "Endpoint", + IsNotNamed: true, + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml index 34a49522b..772b0e8b9 100644 --- a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml @@ -93,15 +93,20 @@ spec: properties: interface: description: interface of the existing endpoint. + enum: + - admin + - internal + - public type: string serviceRef: - description: serviceRef is a reference to which the endpoint - belongs. + description: serviceRef is a reference to the ORC Service + which this resource is associated with. maxLength: 253 minLength: 1 type: string url: description: url is the URL of the existing endpoint. + maxLength: 1024 type: string type: object id: @@ -110,6 +115,7 @@ spec: 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: @@ -150,8 +156,16 @@ spec: resource must be specified if the management policy is `managed`. properties: + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + x-kubernetes-validations: + - message: description is immutable + rule: self == oldSelf enabled: - default: true description: enabled indicates whether the endpoint is enabled or not. type: boolean @@ -162,14 +176,6 @@ spec: - internal - public 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 serviceRef: description: serviceRef is a reference to the ORC Service which this resource is associated with. @@ -181,6 +187,7 @@ spec: rule: self == oldSelf url: description: url is the endpoint URL. + maxLength: 1024 type: string required: - interface @@ -284,26 +291,25 @@ spec: 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: + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string enabled: description: enabled indicates whether the endpoint is enabled or not. type: boolean interface: description: interface indicates the visibility of the endpoint. - enum: - - admin - - internal - - public - type: string - name: - description: name is a Human-readable name for the resource. Might - not be unique. - maxLength: 1024 + maxLength: 128 type: string serviceID: description: serviceID is the ID of the Service to which the resource @@ -312,9 +318,12 @@ spec: type: string url: description: url is the endpoint URL. + maxLength: 1024 type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index b73dcac05..319e67e8a 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,7 @@ # It should be run by config/default resources: - bases/openstack.k-orc.cloud_domains.yaml +- bases/openstack.k-orc.cloud_endpoints.yaml - bases/openstack.k-orc.cloud_flavors.yaml - bases/openstack.k-orc.cloud_floatingips.yaml - bases/openstack.k-orc.cloud_groups.yaml diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 0c5f1c0ea..0b7164e78 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -24,6 +24,11 @@ spec: kind: Domain name: domains.openstack.k-orc.cloud version: v1alpha1 + - description: Endpoint is the Schema for an ORC resource. + displayName: Endpoint + kind: Endpoint + name: endpoints.openstack.k-orc.cloud + version: v1alpha1 - description: Flavor is the Schema for an ORC resource. displayName: Flavor kind: Flavor diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5545c52a6..1bb68f2b9 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -18,6 +18,7 @@ rules: - openstack.k-orc.cloud resources: - domains + - endpoints - flavors - floatingips - groups @@ -49,6 +50,7 @@ rules: - openstack.k-orc.cloud resources: - domains/status + - endpoints/status - flavors/status - floatingips/status - groups/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index aa2a75e43..488fa1eb7 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -2,6 +2,7 @@ ## Append samples of your project ## resources: - openstack_v1alpha1_domain.yaml +- openstack_v1alpha1_endpoint.yaml - openstack_v1alpha1_flavor.yaml - openstack_v1alpha1_floatingip.yaml - openstack_v1alpha1_group.yaml diff --git a/config/samples/openstack_v1alpha1_endpoint.yaml b/config/samples/openstack_v1alpha1_endpoint.yaml index 22dcf1591..9fc4edd13 100644 --- a/config/samples/openstack_v1alpha1_endpoint.yaml +++ b/config/samples/openstack_v1alpha1_endpoint.yaml @@ -5,10 +5,10 @@ metadata: name: endpoint-sample 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: - description: Sample Endpoint - # TODO(scaffolding): Add all fields the resource supports + interface: internal + url: "https://example.com" + serviceRef: service-sample diff --git a/internal/controllers/endpoint/actuator.go b/internal/controllers/endpoint/actuator.go index 13d01ebce..d22213154 100644 --- a/internal/controllers/endpoint/actuator.go +++ b/internal/controllers/endpoint/actuator.go @@ -20,6 +20,7 @@ import ( "context" "iter" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" @@ -71,22 +72,30 @@ func (actuator endpointActuator) ListOSResourcesForAdoption(ctx context.Context, return nil, false } - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter + service, _ := serviceDependency.GetDependency( + ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + + if service == nil { + return nil, false + } + + var filters []osclients.ResourceFilter[osResourceT] + filters = append(filters, func(e *endpoints.Endpoint) bool { + return e.URL == resourceSpec.URL + }) listOpts := endpoints.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Availability: gophercloud.Availability(resourceSpec.Interface), + ServiceID: ptr.Deref(service.Status.ID, ""), } - return actuator.osClient.ListEndpoints(ctx, listOpts), true + return actuator.listOsResources(ctx, listOpts, filters), true } func (actuator endpointActuator) 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 service, rs := dependency.FetchDependency( @@ -100,14 +109,24 @@ func (actuator endpointActuator) ListOSResourcesForImport(ctx context.Context, o return nil, reconcileStatus } + var resourceFilters []osclients.ResourceFilter[osResourceT] + if filter.URL != "" { + resourceFilters = append(resourceFilters, func(e *endpoints.Endpoint) bool { + return e.URL == filter.URL + }) + } + listOpts := endpoints.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - ServiceID: ptr.Deref(service.Status.ID, ""), - // TODO(scaffolding): Add more import filters + ServiceID: ptr.Deref(service.Status.ID, ""), + Availability: gophercloud.Availability(filter.Interface), } - return actuator.osClient.ListEndpoints(ctx, listOpts), reconcileStatus + return actuator.listOsResources(ctx, listOpts, resourceFilters), nil +} + +func (actuator endpointActuator) listOsResources(ctx context.Context, listOpts endpoints.ListOpts, filter []osclients.ResourceFilter[osResourceT]) iter.Seq2[*osResourceT, error] { + endpoints := actuator.osClient.ListEndpoints(ctx, listOpts) + return osclients.Filter(endpoints, filter...) } func (actuator endpointActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -121,23 +140,25 @@ func (actuator endpointActuator) CreateResource(ctx context.Context, obj orcObje var reconcileStatus progress.ReconcileStatus var serviceID string - service, serviceDepRS := serviceDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(serviceDepRS) - if service != nil { - serviceID = ptr.Deref(service.Status.ID, "") - } + service, serviceDepRS := serviceDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + + reconcileStatus = reconcileStatus.WithReconcileStatus(serviceDepRS) + if service != nil { + serviceID = ptr.Deref(service.Status.ID, "") + } if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } createOpts := endpoints.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - ServiceID: serviceID, - // TODO(scaffolding): Add more fields + Availability: gophercloud.Availability(resource.Interface), + Description: ptr.Deref(resource.Description, ""), + Enabled: resource.Enabled, + ServiceID: serviceID, + URL: resource.URL, } osResource, err := actuator.osClient.CreateEndpoint(ctx, createOpts) @@ -167,10 +188,9 @@ func (actuator endpointActuator) updateResource(ctx context.Context, obj orcObje updateOpts := endpoints.UpdateOpts{} - handleNameUpdate(&updateOpts, obj, osResource) - handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleEnabledUpdate(&updateOpts, resource, osResource) + handleURLUpdate(&updateOpts, resource, osResource) + handleInterfaceUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -210,17 +230,24 @@ func needsUpdate(updateOpts endpoints.UpdateOpts) (bool, error) { return len(updateMap) > 0, nil } -func handleNameUpdate(updateOpts *endpoints.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { - name := getResourceName(obj) - if osResource.Name != name { - updateOpts.Name = &name +func handleURLUpdate(updateOpts *endpoints.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + url := resource.URL + if osResource.URL != url { + updateOpts.URL = url + } +} + +func handleInterfaceUpdate(updateOpts *endpoints.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + endpointInterface := gophercloud.Availability(resource.Interface) + if osResource.Availability != endpointInterface { + updateOpts.Availability = endpointInterface } } -func handleDescriptionUpdate(updateOpts *endpoints.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - description := ptr.Deref(resource.Description, "") - if osResource.Description != description { - updateOpts.Description = &description +func handleEnabledUpdate(updateOpts *endpoints.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + enabled := resource.Enabled + if enabled != nil && osResource.Enabled != *enabled { + updateOpts.Enabled = enabled } } diff --git a/internal/controllers/endpoint/actuator_test.go b/internal/controllers/endpoint/actuator_test.go index 3d3f16b4e..e15f8ee50 100644 --- a/internal/controllers/endpoint/actuator_test.go +++ b/internal/controllers/endpoint/actuator_test.go @@ -19,6 +19,7 @@ package endpoint import ( "testing" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/endpoints" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "k8s.io/utils/ptr" @@ -37,7 +38,7 @@ func TestNeedsUpdate(t *testing.T) { }, { name: "Updated opts", - updateOpts: endpoints.UpdateOpts{Name: ptr.To("updated")}, + updateOpts: endpoints.UpdateOpts{URL: "http://updated.com"}, expectChange: true, }, } @@ -52,31 +53,25 @@ func TestNeedsUpdate(t *testing.T) { } } -func TestHandleNameUpdate(t *testing.T) { - ptrToName := ptr.To[orcv1alpha1.OpenStackName] +func TestHandleInterfaceUpdate(t *testing.T) { testCases := []struct { name string - newValue *orcv1alpha1.OpenStackName + newValue *string 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}, + {name: "Identical", newValue: ptr.To("internal"), existingValue: "internal", expectChange: false}, + {name: "Different", newValue: ptr.To("public"), existingValue: "internal", expectChange: true}, + {name: "No value provided, existing is kept", newValue: nil, existingValue: "internal", expectChange: false}, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.Endpoint{} - resource.Name = "object-name" - resource.Spec = orcv1alpha1.EndpointSpec{ - Resource: &orcv1alpha1.EndpointResourceSpec{Name: tt.newValue}, - } - osResource := &osResourceT{Name: tt.existingValue} + resourceSpec := &orcv1alpha1.EndpointResourceSpec{Interface: ptr.Deref(tt.newValue, "")} + osResource := &osResourceT{Availability: gophercloud.Availability(tt.existingValue)} updateOpts := endpoints.UpdateOpts{} - handleNameUpdate(&updateOpts, resource, osResource) + handleInterfaceUpdate(&updateOpts, resourceSpec, osResource) got, _ := needsUpdate(updateOpts) if got != tt.expectChange { @@ -87,27 +82,25 @@ func TestHandleNameUpdate(t *testing.T) { } } -func TestHandleDescriptionUpdate(t *testing.T) { - ptrToDescription := ptr.To[string] +func TestHandleURLUpdate(t *testing.T) { 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}, + {name: "Identical", newValue: ptr.To("http://same.com"), existingValue: "http://same.com", expectChange: false}, + {name: "Different", newValue: ptr.To("http://different.com"), existingValue: "http://same.com", expectChange: true}, + {name: "No value provided, existing is kept", newValue: nil, existingValue: "http://same.com", expectChange: false}, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.EndpointResourceSpec{Description: tt.newValue} - osResource := &osResourceT{Description: tt.existingValue} + resourceSpec := &orcv1alpha1.EndpointResourceSpec{URL: ptr.Deref(tt.newValue, "")} + osResource := &osResourceT{URL: tt.existingValue} updateOpts := endpoints.UpdateOpts{} - handleDescriptionUpdate(&updateOpts, resource, osResource) + handleURLUpdate(&updateOpts, resourceSpec, osResource) got, _ := needsUpdate(updateOpts) if got != tt.expectChange { diff --git a/internal/controllers/endpoint/status.go b/internal/controllers/endpoint/status.go index 86b2031f5..e3f285724 100644 --- a/internal/controllers/endpoint/status.go +++ b/internal/controllers/endpoint/status.go @@ -51,10 +51,9 @@ func (endpointStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Endpo func (endpointStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.EndpointResourceStatus(). WithServiceID(osResource.ServiceID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the EndpointResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + WithEnabled(osResource.Enabled). + WithInterface(string(osResource.Availability)). + WithURL(osResource.URL) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) diff --git a/internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml index 881c9a9f4..0c962cfe7 100644 --- a/internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-create-full/00-assert.yaml @@ -5,9 +5,10 @@ metadata: name: endpoint-create-full status: resource: - name: endpoint-create-full-override - description: Endpoint from "create full" test - # TODO(scaffolding): Add all fields the resource supports + description: "Endpoint description" + interface: internal + url: https://example.com + enabled: false conditions: - type: Available status: "True" @@ -30,4 +31,4 @@ resourceRefs: assertAll: - celExpr: "endpoint.status.id != ''" - celExpr: "endpoint.status.resource.serviceID == service.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "!has(endpoint.status.resource.name)" diff --git a/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml index 59e2202de..aa7360418 100644 --- a/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-create-full/00-create-resource.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-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: {} + resource: + type: endpoint-test --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -18,12 +17,12 @@ metadata: name: endpoint-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 resource: - name: endpoint-create-full-override - description: Endpoint from "create full" test + description: "Endpoint description" serviceRef: endpoint-create-full - # TODO(scaffolding): Add all fields the resource supports + interface: internal + url: https://example.com + enabled: false diff --git a/internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml index 76d25598b..3924e2cf7 100644 --- a/internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-assert.yaml @@ -5,8 +5,9 @@ metadata: name: endpoint-create-minimal status: resource: - name: endpoint-create-minimal - # TODO(scaffolding): Add all fields the resource supports + url: http://example.com + interface: internal + enabled: true conditions: - type: Available status: "True" @@ -29,4 +30,4 @@ resourceRefs: assertAll: - celExpr: "endpoint.status.id != ''" - celExpr: "endpoint.status.resource.serviceID == service.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "!has(endpoint.status.resource.name)" diff --git a/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml index 54dd56712..48b59dc12 100644 --- a/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-create-minimal/00-create-resource.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-create-minimal 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: {} + resource: + type: endpoint-test --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -18,11 +17,10 @@ metadata: name: endpoint-create-minimal 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): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: serviceRef: endpoint-create-minimal + interface: internal + url: http://example.com diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml index ab2e0bd3a..625057953 100644 --- a/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/endpoint/tests/endpoint-dependency/00-create-resources-missing-deps.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-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: {} + resource: + type: endpoint-test --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -18,14 +17,13 @@ metadata: name: endpoint-dependency-no-service 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: serviceRef: endpoint-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the resource - + interface: internal + url: http://example.com --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -33,10 +31,10 @@ metadata: name: endpoint-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: endpoint-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: serviceRef: endpoint-dependency + interface: internal + url: http://example.com diff --git a/internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml b/internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml index 4d89ceeb0..103c5b682 100644 --- a/internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml +++ b/internal/controllers/endpoint/tests/endpoint-dependency/01-create-dependencies.yaml @@ -11,9 +11,8 @@ metadata: name: endpoint-dependency-pending 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: {} + resource: + type: endpoint-test diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml index 6a5c8737b..76cddcd65 100644 --- a/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/00-import-resource.yaml @@ -5,7 +5,7 @@ metadata: name: endpoint-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -18,9 +18,11 @@ metadata: name: endpoint-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: serviceRef: endpoint-import-dependency + interface: internal + url: http://example.com diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml index 55f8cede6..a7c71eceb 100644 --- a/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/01-create-trap-resource.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-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: {} + resource: + type: endpoint-import-dependency-not-this-one --- # This `endpoint-import-dependency-not-this-one` should not be picked by the import filter apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -19,10 +18,10 @@ metadata: name: endpoint-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: serviceRef: endpoint-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + interface: internal + url: http://example.com diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml index 55de8276b..65786fa03 100644 --- a/internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/02-create-resource.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-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: {} + resource: + type: endpoint-import-dependency-external --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -18,10 +17,10 @@ metadata: name: endpoint-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: serviceRef: endpoint-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + interface: internal + url: http://example.com diff --git a/internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml index 5c513bef5..43afeab6f 100644 --- a/internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import-error/00-create-resources.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-import-error 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: {} + resource: + type: endpoint-import-error --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -18,14 +17,13 @@ metadata: name: endpoint-import-error-external-1 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: - description: Endpoint from "import error" test serviceRef: endpoint-import-error - # TODO(scaffolding): add any required field + interface: internal + url: http://example1.com --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -33,11 +31,10 @@ metadata: name: endpoint-import-error-external-2 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: - description: Endpoint from "import error" test serviceRef: endpoint-import-error - # TODO(scaffolding): add any required field + interface: internal + url: http://example2.com diff --git a/internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml index df0e2d3a9..0b106e2cd 100644 --- a/internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import-error/01-import-resource.yaml @@ -5,9 +5,10 @@ metadata: name: endpoint-import-error spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - description: Endpoint from "import error" test + serviceRef: endpoint-import-error + interface: internal diff --git a/internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml index cdfda600d..e42eeedd3 100644 --- a/internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import/00-import-resource.yaml @@ -1,15 +1,28 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: endpoint-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + type: endpoint-import +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint metadata: name: endpoint-import spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - name: endpoint-import-external - description: Endpoint endpoint-import-external from "endpoint-import" test - # TODO(scaffolding): Add all fields supported by the filter + serviceRef: endpoint-import + interface: internal + url: http://example.com + enabled: false diff --git a/internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml index 63a67bdbd..a7983cc2c 100644 --- a/internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import/01-assert.yaml @@ -14,9 +14,8 @@ status: status: "False" reason: Success resource: - name: endpoint-import-external-not-this-one - description: Endpoint endpoint-import-external from "endpoint-import" test - # TODO(scaffolding): Add fields necessary to match filter + interface: internal + url: http://example.com --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint diff --git a/internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml index 21a09ab23..b788e3f82 100644 --- a/internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import/01-create-trap-resource.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-import-external-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: {} + resource: + type: endpoint-import-external-not-this-one --- # This `endpoint-import-external-not-this-one` resource serves two purposes: # - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) @@ -21,11 +20,10 @@ metadata: name: endpoint-import-external-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: - description: Endpoint endpoint-import-external from "endpoint-import" test serviceRef: endpoint-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter + interface: internal + url: http://example.com diff --git a/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml b/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml index b3c7c0b04..a776882ab 100644 --- a/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import/02-assert.yaml @@ -10,6 +10,10 @@ resourceRefs: kind: Endpoint name: endpoint-import-external-not-this-one ref: endpoint2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-import + ref: service assertAll: - celExpr: "endpoint1.status.id != endpoint2.status.id" --- @@ -28,6 +32,6 @@ status: status: "False" reason: Success resource: - name: endpoint-import-external - description: Endpoint endpoint-import-external from "endpoint-import" test - # TODO(scaffolding): Add all fields the resource supports + interface: internal + url: http://example.com + enabled: false diff --git a/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml b/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml index dcc1e86b8..499ff7783 100644 --- a/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-import/02-create-resource.yaml @@ -1,28 +1,15 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Service -metadata: - name: endpoint-import -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: Endpoint metadata: name: endpoint-import-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 resource: - description: Endpoint endpoint-import-external from "endpoint-import" test serviceRef: endpoint-import - # TODO(scaffolding): Add fields necessary to match filter + interface: internal + url: http://example.com + enabled: false diff --git a/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml index 49cc482c0..0714459c2 100644 --- a/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-update/00-assert.yaml @@ -1,22 +1,13 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Endpoint - name: endpoint-update - ref: endpoint -assertAll: - - celExpr: "!has(endpoint.status.resource.description)" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint metadata: name: endpoint-update status: resource: - name: endpoint-update - # TODO(scaffolding): Add matches for more fields + interface: internal + url: http://example.com + enabled: false conditions: - type: Available status: "True" @@ -24,3 +15,19 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Endpoint + name: endpoint-update + ref: endpoint + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-update + ref: service +assertAll: + - celExpr: "endpoint.status.resource.serviceID == service.status.id" + - celExpr: "!has(endpoint.status.resource.name)" + - celExpr: "!has(endpoint.status.resource.description)" diff --git a/internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml index cdb8d1be4..535c94d1a 100644 --- a/internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-update/00-minimal-resource.yaml @@ -5,12 +5,11 @@ metadata: name: endpoint-update 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: {} + resource: + type: endpoint-test-update --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Endpoint @@ -18,11 +17,13 @@ metadata: name: endpoint-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated - cloudName: openstack + cloudName: openstack-admin 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: serviceRef: endpoint-update + interface: internal + url: http://example.com + # Set a different value than the default so we can update it + # later. + enabled: false diff --git a/internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml b/internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/endpoint/tests/endpoint-update/00-prerequisites.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -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/endpoint/tests/endpoint-update/01-assert.yaml b/internal/controllers/endpoint/tests/endpoint-update/01-assert.yaml index e526907c7..4d03c34d6 100644 --- a/internal/controllers/endpoint/tests/endpoint-update/01-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-update/01-assert.yaml @@ -5,9 +5,9 @@ metadata: name: endpoint-update status: resource: - name: endpoint-update-updated - description: endpoint-update-updated - # TODO(scaffolding): match all fields that were modified + interface: public + url: http://example.com/updated + enabled: true conditions: - type: Available status: "True" diff --git a/internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml b/internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml index ea78d64af..c95d344e3 100644 --- a/internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml +++ b/internal/controllers/endpoint/tests/endpoint-update/01-updated-resource.yaml @@ -5,6 +5,6 @@ metadata: name: endpoint-update spec: resource: - name: endpoint-update-updated - description: endpoint-update-updated - # TODO(scaffolding): update all mutable fields + interface: public + url: http://example.com/updated + enabled: true diff --git a/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml b/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml index c3e8f879e..f41c5016e 100644 --- a/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml +++ b/internal/controllers/endpoint/tests/endpoint-update/02-assert.yaml @@ -6,7 +6,13 @@ resourceRefs: kind: Endpoint name: endpoint-update ref: endpoint + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Service + name: endpoint-update + ref: service assertAll: + - celExpr: "endpoint.status.resource.serviceID == service.status.id" + - celExpr: "!has(endpoint.status.resource.name)" - celExpr: "!has(endpoint.status.resource.description)" --- apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -15,8 +21,9 @@ metadata: name: endpoint-update status: resource: - name: endpoint-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + interface: internal + url: http://example.com + enabled: false conditions: - type: Available status: "True" diff --git a/internal/controllers/endpoint/zz_generated.adapter.go b/internal/controllers/endpoint/zz_generated.adapter.go index 934d9b7da..fe95ab43f 100644 --- a/internal/controllers/endpoint/zz_generated.adapter.go +++ b/internal/controllers/endpoint/zz_generated.adapter.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. @@ -76,13 +76,3 @@ func (f adapterT) GetImportFilter() *filterT { } 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/endpoint/zz_generated.controller.go b/internal/controllers/endpoint/zz_generated.controller.go index 1dd55a109..e0ccac2f9 100644 --- a/internal/controllers/endpoint/zz_generated.controller.go +++ b/internal/controllers/endpoint/zz_generated.controller.go @@ -1,6 +1,6 @@ // Code generated by resource-generator. DO NOT EDIT. /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/controllers/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index fef380932..9a20be882 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -2,17 +2,17 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: port - name: port-update - ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: port + name: port-update + ref: port assertAll: - - celExpr: "port.status.id != ''" - - celExpr: "port.status.resource.createdAt != ''" - - celExpr: "port.status.resource.updatedAt != ''" - - celExpr: "port.status.resource.macAddress != ''" - - celExpr: "!has(port.status.resource.fixedIPs)" - - celExpr: "!has(port.status.resource.description)" + - celExpr: "port.status.id != ''" + - celExpr: "port.status.resource.createdAt != ''" + - celExpr: "port.status.resource.updatedAt != ''" + - celExpr: "port.status.resource.macAddress != ''" + - celExpr: "!has(port.status.resource.fixedIPs)" + - celExpr: "!has(port.status.resource.description)" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index c8f79187b..37a622ec7 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -48,4 +48,4 @@ status: reason: Success - type: Progressing status: "False" - reason: Success + reason: Success diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 0da3b97ba..176f3bc93 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -38,6 +38,9 @@ import ( //go:generate mockgen -package mock -destination=domain.go -source=../domain.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock DomainClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt domain.go > _domain.go && mv _domain.go domain.go" +//go:generate mockgen -package mock -destination=endpoint.go -source=../endpoint.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock EndpointClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt endpoint.go > _endpoint.go && mv _endpoint.go endpoint.go" + //go:generate mockgen -package mock -destination=group.go -source=../group.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock GroupClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt group.go > _group.go && mv _group.go group.go" diff --git a/internal/osclients/mock/endpoint.go b/internal/osclients/mock/endpoint.go index dafc92276..ed4ea1be4 100644 --- a/internal/osclients/mock/endpoint.go +++ b/internal/osclients/mock/endpoint.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/internal/scope/mock.go b/internal/scope/mock.go index ef959fae5..9cc49cd03 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -36,6 +36,7 @@ import ( type MockScopeFactory struct { ComputeClient *mock.MockComputeClient DomainClient *mock.MockDomainClient + EndpointClient *mock.MockEndpointClient GroupClient *mock.MockGroupClient IdentityClient *mock.MockIdentityClient ImageClient *mock.MockImageClient @@ -52,6 +53,7 @@ type MockScopeFactory struct { func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { computeClient := mock.NewMockComputeClient(mockCtrl) domainClient := mock.NewMockDomainClient(mockCtrl) + endpointClient := mock.NewMockEndpointClient(mockCtrl) groupClient := mock.NewMockGroupClient(mockCtrl) identityClient := mock.NewMockIdentityClient(mockCtrl) imageClient := mock.NewMockImageClient(mockCtrl) @@ -65,6 +67,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { return &MockScopeFactory{ ComputeClient: computeClient, DomainClient: domainClient, + EndpointClient: endpointClient, GroupClient: groupClient, IdentityClient: identityClient, ImageClient: imageClient, @@ -132,6 +135,10 @@ func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) { return f.RoleClient, nil } +func (f *MockScopeFactory) NewEndpointClient() (osclients.EndpointClient, error) { + return f.EndpointClient, nil +} + func (f *MockScopeFactory) ExtractToken() (*tokens.Token, error) { return &tokens.Token{ExpiresAt: time.Now().Add(24 * time.Hour)}, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index 65670ba60..d9853e381 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -169,6 +169,10 @@ func (s *providerScope) NewServiceClient() (clients.ServiceClient, error) { return clients.NewServiceClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewEndpointClient() (clients.EndpointClient, error) { + return clients.NewEndpointClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) NewKeyPairClient() (clients.KeyPairClient, error) { return clients.NewKeyPairClient(s.providerClient, s.providerClientOpts) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 7da50dc8f..8baa7f404 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -50,6 +50,7 @@ type Factory interface { type Scope interface { NewComputeClient() (osclients.ComputeClient, error) NewDomainClient() (osclients.DomainClient, error) + NewEndpointClient() (osclients.EndpointClient, error) NewGroupClient() (osclients.GroupClient, error) NewIdentityClient() (osclients.IdentityClient, error) NewImageClient() (osclients.ImageClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 67828d420..10cedb065 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -3,6 +3,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: - ./internal/controllers/domain/tests/ +- ./internal/controllers/endpoint/tests/ - ./internal/controllers/flavor/tests/ - ./internal/controllers/floatingip/tests/ - ./internal/controllers/group/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go index a099f728f..6c1f5e897 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpoint.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go index 0908e2c55..fe33276e4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointfilter.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. @@ -25,8 +25,8 @@ import ( // EndpointFilterApplyConfiguration represents a declarative configuration of the EndpointFilter type for use // with apply. type EndpointFilterApplyConfiguration struct { - ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` Interface *string `json:"interface,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` URL *string `json:"url,omitempty"` } @@ -36,14 +36,6 @@ func EndpointFilter() *EndpointFilterApplyConfiguration { return &EndpointFilterApplyConfiguration{} } -// WithServiceRef sets the ServiceRef field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ServiceRef field is set to the value of the last call. -func (b *EndpointFilterApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *EndpointFilterApplyConfiguration { - b.ServiceRef = &value - return b -} - // WithInterface sets the Interface field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Interface field is set to the value of the last call. @@ -52,6 +44,14 @@ func (b *EndpointFilterApplyConfiguration) WithInterface(value string) *Endpoint return b } +// WithServiceRef sets the ServiceRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceRef field is set to the value of the last call. +func (b *EndpointFilterApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *EndpointFilterApplyConfiguration { + b.ServiceRef = &value + return b +} + // WithURL sets the URL field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the URL field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go index e20a99cd7..8d6cae433 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointimport.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go index b9f157902..ff59ccce4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcespec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. @@ -25,11 +25,11 @@ import ( // EndpointResourceSpecApplyConfiguration represents a declarative configuration of the EndpointResourceSpec type for use // with apply. type EndpointResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Interface *string `json:"interface,omitempty"` - URL *string `json:"url,omitempty"` - ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` + Description *string `json:"description,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Interface *string `json:"interface,omitempty"` + URL *string `json:"url,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` } // EndpointResourceSpecApplyConfiguration constructs a declarative configuration of the EndpointResourceSpec type for use with @@ -38,11 +38,11 @@ func EndpointResourceSpec() *EndpointResourceSpecApplyConfiguration { return &EndpointResourceSpecApplyConfiguration{} } -// WithName sets the Name field in the declarative configuration to the given value +// 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 Name field is set to the value of the last call. -func (b *EndpointResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *EndpointResourceSpecApplyConfiguration { - b.Name = &value +// If called multiple times, the Description field is set to the value of the last call. +func (b *EndpointResourceSpecApplyConfiguration) WithDescription(value string) *EndpointResourceSpecApplyConfiguration { + b.Description = &value return b } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go index 8a0b7c87c..54a98b5a7 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointresourcestatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. @@ -21,11 +21,11 @@ package v1alpha1 // EndpointResourceStatusApplyConfiguration represents a declarative configuration of the EndpointResourceStatus type for use // with apply. type EndpointResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Interface *string `json:"interface,omitempty"` - URL *string `json:"url,omitempty"` - ServiceID *string `json:"serviceID,omitempty"` + Description *string `json:"description,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Interface *string `json:"interface,omitempty"` + URL *string `json:"url,omitempty"` + ServiceID *string `json:"serviceID,omitempty"` } // EndpointResourceStatusApplyConfiguration constructs a declarative configuration of the EndpointResourceStatus type for use with @@ -34,11 +34,11 @@ func EndpointResourceStatus() *EndpointResourceStatusApplyConfiguration { return &EndpointResourceStatusApplyConfiguration{} } -// WithName sets the Name field in the declarative configuration to the given value +// 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 Name field is set to the value of the last call. -func (b *EndpointResourceStatusApplyConfiguration) WithName(value string) *EndpointResourceStatusApplyConfiguration { - b.Name = &value +// If called multiple times, the Description field is set to the value of the last call. +func (b *EndpointResourceStatusApplyConfiguration) WithDescription(value string) *EndpointResourceStatusApplyConfiguration { + b.Description = &value return b } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go index fbe73d129..198237c30 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go index ab14837ef..d620075a5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 2884d6d23..87e4f6e86 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -192,6 +192,121 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.DomainResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Endpoint + 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.EndpointSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointFilter + map: + fields: + - name: interface + type: + scalar: string + - name: serviceRef + type: + scalar: string + - name: url + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointResourceSpec + map: + fields: + - name: description + type: + scalar: string + - name: enabled + type: + scalar: boolean + - name: interface + type: + scalar: string + - name: serviceRef + type: + scalar: string + - name: url + type: + scalar: string + default: "" +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointResourceStatus + map: + fields: + - name: description + type: + scalar: string + - name: enabled + type: + scalar: boolean + - name: interface + type: + scalar: string + - name: serviceID + type: + scalar: string + - name: url + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointSpec + 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.EndpointImport + - 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.EndpointResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointStatus + 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.EndpointResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ExternalGateway map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 882509c7a..1b58223cf 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -58,6 +58,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.DomainSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("DomainStatus"): return &apiv1alpha1.DomainStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Endpoint"): + return &apiv1alpha1.EndpointApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("EndpointFilter"): + return &apiv1alpha1.EndpointFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("EndpointImport"): + return &apiv1alpha1.EndpointImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("EndpointResourceSpec"): + return &apiv1alpha1.EndpointResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("EndpointResourceStatus"): + return &apiv1alpha1.EndpointResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("EndpointSpec"): + return &apiv1alpha1.EndpointSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("EndpointStatus"): + return &apiv1alpha1.EndpointStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ExternalGateway"): return &apiv1alpha1.ExternalGatewayApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ExternalGatewayStatus"): 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 91ee2fdb4..7c2e4e67d 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -29,6 +29,7 @@ import ( type OpenstackV1alpha1Interface interface { RESTClient() rest.Interface DomainsGetter + EndpointsGetter FlavorsGetter FloatingIPsGetter GroupsGetter @@ -59,6 +60,10 @@ func (c *OpenstackV1alpha1Client) Domains(namespace string) DomainInterface { return newDomains(c, namespace) } +func (c *OpenstackV1alpha1Client) Endpoints(namespace string) EndpointInterface { + return newEndpoints(c, namespace) +} + func (c *OpenstackV1alpha1Client) Flavors(namespace string) FlavorInterface { return newFlavors(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go index 8c0f3c58b..4eda9ea43 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/endpoint.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. 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 81e95d4c9..2b7ba89cc 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 @@ -32,6 +32,10 @@ func (c *FakeOpenstackV1alpha1) Domains(namespace string) v1alpha1.DomainInterfa return newFakeDomains(c, namespace) } +func (c *FakeOpenstackV1alpha1) Endpoints(namespace string) v1alpha1.EndpointInterface { + return newFakeEndpoints(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Flavors(namespace string) v1alpha1.FlavorInterface { return newFakeFlavors(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go index bc2842cde..ab36dca23 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_endpoint.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. 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 ca41372ee..e34607a4b 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -20,6 +20,8 @@ package v1alpha1 type DomainExpansion interface{} +type EndpointExpansion interface{} + type FlavorExpansion interface{} type FloatingIPExpansion interface{} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go b/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go index 496b05405..7deada74f 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/endpoint.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 28e76d19c..c9f62ae9c 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -26,6 +26,8 @@ import ( type Interface interface { // Domains returns a DomainInformer. Domains() DomainInformer + // Endpoints returns a EndpointInformer. + Endpoints() EndpointInformer // Flavors returns a FlavorInformer. Flavors() FlavorInformer // FloatingIPs returns a FloatingIPInformer. @@ -82,6 +84,11 @@ func (v *version) Domains() DomainInformer { return &domainInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Endpoints returns a EndpointInformer. +func (v *version) Endpoints() EndpointInformer { + return &endpointInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Flavors returns a FlavorInformer. func (v *version) Flavors() FlavorInformer { return &flavorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index f83a355c4..a2cd276ae 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -55,6 +55,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=openstack.k-orc.cloud, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("domains"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Domains().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("endpoints"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Endpoints().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("flavors"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Flavors().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("floatingips"): diff --git a/pkg/clients/listers/api/v1alpha1/endpoint.go b/pkg/clients/listers/api/v1alpha1/endpoint.go index 427f09149..1d7599408 100644 --- a/pkg/clients/listers/api/v1alpha1/endpoint.go +++ b/pkg/clients/listers/api/v1alpha1/endpoint.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The ORC Authors. +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. diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 722c92a5b..e2fc3b2d2 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -26,6 +26,14 @@ type DomainListerExpansion interface{} // DomainNamespaceLister. type DomainNamespaceListerExpansion interface{} +// EndpointListerExpansion allows custom methods to be added to +// EndpointLister. +type EndpointListerExpansion interface{} + +// EndpointNamespaceListerExpansion allows custom methods to be added to +// EndpointNamespaceLister. +type EndpointNamespaceListerExpansion interface{} + // FlavorListerExpansion allows custom methods to be added to // FlavorLister. type FlavorListerExpansion interface{} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 2c2b9b8e8..18bf9bf9e 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -11,6 +11,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API ### Resource Types - [Domain](#domain) +- [Endpoint](#endpoint) - [Flavor](#flavor) - [FloatingIP](#floatingip) - [Group](#group) @@ -165,6 +166,7 @@ CloudCredentialsReference is a reference to a secret containing OpenStack creden _Appears in:_ - [DomainSpec](#domainspec) +- [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) - [FloatingIPSpec](#floatingipspec) - [GroupSpec](#groupspec) @@ -337,6 +339,142 @@ _Appears in:_ | `resource` _[DomainResourceStatus](#domainresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +#### Endpoint + + + +Endpoint is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `Endpoint` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[EndpointSpec](#endpointspec)_ | spec specifies the desired state of the resource. | | | +| `status` _[EndpointStatus](#endpointstatus)_ | status defines the observed state of the resource. | | | + + +#### EndpointFilter + + + +EndpointFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [EndpointImport](#endpointimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `interface` _string_ | interface of the existing endpoint. | | Enum: [admin internal public]
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `url` _string_ | url is the URL of the existing endpoint. | | MaxLength: 1024
| + + +#### EndpointImport + + + +EndpointImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [EndpointSpec](#endpointspec) + +| 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
| +| `filter` _[EndpointFilter](#endpointfilter)_ | 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
| + + +#### EndpointResourceSpec + + + +EndpointResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [EndpointSpec](#endpointspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| +| `enabled` _boolean_ | enabled indicates whether the endpoint is enabled or not. | | | +| `interface` _string_ | interface indicates the visibility of the endpoint. | | Enum: [admin internal public]
| +| `url` _string_ | url is the endpoint URL. | | MaxLength: 1024
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
| + + +#### EndpointResourceStatus + + + +EndpointResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [EndpointStatus](#endpointstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| +| `enabled` _boolean_ | enabled indicates whether the endpoint is enabled or not. | | | +| `interface` _string_ | interface indicates the visibility of the endpoint. | | MaxLength: 128
| +| `url` _string_ | url is the endpoint URL. | | MaxLength: 1024
| +| `serviceID` _string_ | serviceID is the ID of the Service to which the resource is associated. | | MaxLength: 1024
| + + +#### EndpointSpec + + + +EndpointSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [Endpoint](#endpoint) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[EndpointImport](#endpointimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[EndpointResourceSpec](#endpointresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | + + +#### EndpointStatus + + + +EndpointStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [Endpoint](#endpoint) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| +| `resource` _[EndpointResourceStatus](#endpointresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + #### Ethertype _Underlying type:_ _string_ @@ -1634,6 +1772,8 @@ _Validation:_ _Appears in:_ - [Address](#address) +- [EndpointFilter](#endpointfilter) +- [EndpointResourceSpec](#endpointresourcespec) - [ExternalGateway](#externalgateway) - [FloatingIPFilter](#floatingipfilter) - [FloatingIPResourceSpec](#floatingipresourcespec) @@ -1704,6 +1844,7 @@ _Appears in:_ _Appears in:_ - [DomainSpec](#domainspec) +- [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) - [FloatingIPSpec](#floatingipspec) - [GroupSpec](#groupspec) @@ -1739,6 +1880,7 @@ _Validation:_ _Appears in:_ - [DomainSpec](#domainspec) +- [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) - [FloatingIPSpec](#floatingipspec) - [GroupSpec](#groupspec) From c367671c2d407f413c02361ebb7d7471c9011fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 16 Feb 2026 11:16:30 +0100 Subject: [PATCH 053/121] Bump go to 1.25.7 1.24 is no longer supported. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f1cb5a12..ef8521165 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ BUNDLE_IMG ?= bundle:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.29.0 TRIVY_VERSION = 0.49.1 -GO_VERSION ?= 1.24.12 +GO_VERSION ?= 1.25.7 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) From d8176a75d9bba014cd20f97c7504e92ba2216362 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Mon, 9 Feb 2026 10:45:55 +0200 Subject: [PATCH 054/121] Add AI agent instructions and skills using open standards Add AGENTS.md with project-specific instructions for AI-assisted development of ORC controllers, including project structure, key patterns, and references to detailed documentation. CLAUDE.md is a symlink for Claude Code compatibility. Add skills for common development workflows in .agents/skills/: - /new-controller: Scaffold and implement new ORC controllers - /update-controller: Modify existing controllers (add fields, tags, etc.) - /add-dependency: Add resource dependencies to controllers - /proposal: Write enhancement proposals following the template - /testing: Run unit tests, linting, and E2E tests Skills follow the Agent Skills open standard (SKILL.md format). .claude/skills/ symlinks to .agents/skills/ for Claude Code support. Co-Authored-By: Claude Opus 4.5 --- .agents/skills/add-dependency/SKILL.md | 250 ++++++++++++++++++++ .agents/skills/new-controller/SKILL.md | 248 ++++++++++++++++++++ .agents/skills/new-controller/patterns.md | 168 ++++++++++++++ .agents/skills/proposal/SKILL.md | 251 ++++++++++++++++++++ .agents/skills/testing/SKILL.md | 107 +++++++++ .agents/skills/update-controller/SKILL.md | 249 ++++++++++++++++++++ .claude/settings.local.json | 14 ++ .claude/skills | 1 + AGENTS.md | 264 ++++++++++++++++++++++ CLAUDE.md | 1 + 10 files changed, 1553 insertions(+) create mode 100644 .agents/skills/add-dependency/SKILL.md create mode 100644 .agents/skills/new-controller/SKILL.md create mode 100644 .agents/skills/new-controller/patterns.md create mode 100644 .agents/skills/proposal/SKILL.md create mode 100644 .agents/skills/testing/SKILL.md create mode 100644 .agents/skills/update-controller/SKILL.md create mode 100644 .claude/settings.local.json create mode 120000 .claude/skills create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/.agents/skills/add-dependency/SKILL.md b/.agents/skills/add-dependency/SKILL.md new file mode 100644 index 000000000..f657ab8f7 --- /dev/null +++ b/.agents/skills/add-dependency/SKILL.md @@ -0,0 +1,250 @@ +--- +name: add-dependency +description: Add a dependency on another ORC resource to a controller. Use when a resource needs to reference or wait for another resource (e.g., Subnet depends on Network). +disable-model-invocation: true +--- + +# Add Dependency to Controller + +Guide for adding a dependency on another ORC resource. + +**Reference**: See `website/docs/development/controller-implementation.md` for detailed rationale on dependency patterns. + +## When to Use Dependencies + +Use a dependency when your controller needs to: +- Wait for another resource to be available before creating +- Reference another resource's OpenStack ID +- Optionally prevent deletion of a resource that's still in use (deletion guard) + +## Key Principles + +See also "Dependency Timing" in @.agents/skills/new-controller/patterns.md + +### 1. Resolve Dependencies Late + +Resolve dependencies as late as possible, as close to the point of use as possible. This reduces coupling and gives users flexibility when fixing failed deployments. + +**Examples:** +- Subnet depends on Network for creation, but NOT for import by ID or after `status.ID` is set +- Don't require recreating a deleted Network just to delete a Subnet +- Add finalizers only immediately before the OpenStack create/update call + +### 2. Choose the Right Dependency Type + +| Type | Use When | Example | +|------|----------|---------| +| **Normal** (`NewDependency`) | Dependency is optional OR deletion is allowed by OpenStack | Import filter refs, Flavor ref | +| **Deletion Guard** (`NewDeletionGuardDependency`) | Deletion would fail or corrupt your resource | Subnet→Network, Port→Subnet | + +### 3. Use Descriptive Names + +When multiple dependencies of the same type exist, use descriptive prefixes: +- `vipSubnetDependency` not `subnetDependency` (when there could be other subnet refs) +- `sourcePortDependency` vs `destinationPortDependency` +- `memberNetworkDependency` vs `externalNetworkDependency` + +## Dependency Types + +### Normal Dependency +Wait for resource but don't prevent deletion: +```go +dependency.NewDependency[*orcv1alpha1.MyResourceList, *orcv1alpha1.DepResource](...) +``` + +### Deletion Guard Dependency +Wait for resource AND prevent its deletion: +```go +dependency.NewDeletionGuardDependency[*orcv1alpha1.MyResourceList, *orcv1alpha1.DepResource](...) +``` + +**Use Deletion Guard when**: Deleting the dependency would cause your resource to fail or become invalid (e.g., Subnet depends on Network, Port depends on SecurityGroup). + +## Step 1: Add Reference Field to API + +In `api/v1alpha1/_types.go`, add the reference field: + +```go +type MyResourceSpec struct { + // ... + + // projectRef is a reference to a Project. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` +} +``` + +For import filters, add to the Filter struct as well: +```go +type MyResourceFilter struct { + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` +} +``` + +## Step 2: Declare Dependency + +In `internal/controllers//controller.go`, add package-scoped variable: + +```go +var ( + projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.MyResourceList, *orcv1alpha1.Project]( + "spec.resource.projectRef", // Field path for indexing + func(obj *orcv1alpha1.MyResource) []string { + resource := obj.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, + ) + + // For import filter dependencies (no deletion guard needed) + projectImportDependency = dependency.NewDependency[*orcv1alpha1.MyResourceList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(obj *orcv1alpha1.MyResource) []string { + imp := obj.Spec.Import + if imp == nil || imp.Filter == nil || imp.Filter.ProjectRef == nil { + return nil + } + return []string{string(*imp.Filter.ProjectRef)} + }, + ) +) +``` + +## Step 3: Setup Watches + +In `SetupWithManager()` in `controller.go`: + +```go +func (c myReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + // Create watch handlers + projectWatchHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + For(&orcv1alpha1.MyResource{}). + // Watch the dependency + Watches(&orcv1alpha1.Project{}, projectWatchHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ) + + // Register dependencies with manager + if err := errors.Join( + projectDependency.AddToManager(ctx, mgr), + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, helperFactory{}, statusWriter{}) + return builder.Complete(&r) +} +``` + +## Step 4: Use Dependency in Actuator + +In `actuator.go`, resolve the dependency before using it: + +```go +func (actuator myActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.MyResource) (*osResourceT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + var projectID string + if resource.ProjectRef != nil { + project, reconcileStatus := projectDependency.GetDependency( + ctx, actuator.k8sClient, obj, + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + projectID = ptr.Deref(project.Status.ID, "") + } + + createOpts := myresource.CreateOpts{ + ProjectID: projectID, + // ... + } + // ... +} +``` + +For import filter dependencies: +```go +func (actuator myActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + var reconcileStatus progress.ReconcileStatus + + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := myresource.ListOpts{ + ProjectID: ptr.Deref(project.Status.ID, ""), + } + return actuator.osClient.ListMyResources(ctx, listOpts), nil +} +``` + +## Step 5: Add k8sClient to Actuator + +If not already present, add `k8sClient` to the actuator struct: + +```go +type myActuator struct { + osClient osclients.MyResourceClient + k8sClient client.Client // Add this +} +``` + +Update `newActuator()`: +```go +func newActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (myActuator, progress.ReconcileStatus) { + k8sClient := controller.GetK8sClient() // Add this + // ... + return myActuator{ + osClient: osClient, + k8sClient: k8sClient, // Add this + }, nil +} +``` + +## Step 6: Add Tests + +Create dependency tests in `internal/controllers//tests/-dependency/`: +- Test that resource waits for dependency +- Test that dependency deletion is blocked (if using DeletionGuard) + +Follow @.agents/skills/testing/SKILL.md for running unit tests, linting, and E2E tests. + +## Checklist + +- [ ] Reference field added to API types (with immutability validation) +- [ ] Dependency declared in controller.go +- [ ] Watch configured in SetupWithManager +- [ ] Dependency registered with manager (AddToManager) +- [ ] Dependency resolved in actuator before use +- [ ] k8sClient added to actuator struct +- [ ] `make generate` runs cleanly +- [ ] `make lint` passes +- [ ] Dependency tests added diff --git a/.agents/skills/new-controller/SKILL.md b/.agents/skills/new-controller/SKILL.md new file mode 100644 index 000000000..352849859 --- /dev/null +++ b/.agents/skills/new-controller/SKILL.md @@ -0,0 +1,248 @@ +--- +name: new-controller +description: Create a new ORC controller for an OpenStack resource. Use when adding support for a new OpenStack resource type (e.g., LoadBalancer, FloatingIP). +disable-model-invocation: true +--- + +# Create New Controller + +Create a new ORC controller for an OpenStack resource. + +**IMPORTANT**: Complete ALL steps in order. Do not stop after implementing TODOs - you must also write E2E tests and run them. Ask the user for `E2E_OSCLOUDS` path if needed to run tests. + +## Prerequisites + +Ask the user one by one about: +1. What OpenStack resource to create (e.g., "VolumeBackup") +2. Which service it belongs to (compute, network, blockstorage, identity, image) +3. Does it need polling for availability or deletion? (i.e., does the resource have intermediate provisioning states like PENDING_CREATE, BUILD, etc.) +4. Any dependencies on other ORC resources (required, optional, or import-only)? +5. Is there a similar existing controller to use as reference? (e.g., Listener for LoadBalancer) +6. Do they have `E2E_OSCLOUDS` path to a clouds.yaml for running E2E tests locally? (If not, local E2E testing will be skipped) +7. Any additional requirements or constraints? (e.g., cascade delete support, special validation rules, immutability requirements) + +## Step 1: Research the OpenStack Resource + +**Before scaffolding**, research the resource to understand the exact field names: + +1. **Read the gophercloud struct** to get exact field names: +```bash +go doc . +go doc .CreateOpts +``` + +2. **Look at a similar existing controller** for patterns (if user provided one): + - Check their `*_types.go` for API structure + - Check their `actuator.go` for implementation patterns + +3. **Note the exact field names** from gophercloud - use these when defining API types: + - If gophercloud has `VipSubnetID`, name the ORC field `VipSubnetRef` (not just `SubnetRef`) + - If gophercloud has `FlavorID`, name the ORC field `FlavorRef` + - Preserve prefixes like `Vip`, `Source`, `Destination` etc. + +## Step 2: Run Scaffolding Tool + +**IMPORTANT**: Build a single scaffolding command using the user's answers and the flags reference below. Run it exactly ONCE (user will be prompted to approve). + +Use the field names discovered in Step 1 to inform your implementation later. + +### Scaffolding Flags Reference + +**Required flags:** + +| Flag | Description | Example | +|------|-------------|---------| +| `-kind` | The Kind of the new resource (PascalCase) | `VolumeBackup`, `FloatingIP` | +| `-gophercloud-client` | The gophercloud function to instantiate a client | `NewBlockStorageV3`, `NewNetworkV2` | +| `-gophercloud-module` | Full gophercloud module import path | `github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups` | + +**Optional flags:** + +| Flag | Description | Default | +|------|-------------|---------| +| `-gophercloud-type` | The gophercloud struct type name | Same as `-kind` | +| `-openstack-json-object` | Object name in OpenStack JSON responses | snake_case of kind (e.g., `volume_backup`) | +| `-available-polling-period` | Polling period in seconds while waiting for resource to become available | `0` (available immediately) | +| `-deleting-polling-period` | Polling period in seconds while waiting for resource to be deleted | `0` (deleted immediately) | +| `-required-create-dependency` | Required dependency for creation (can repeat flag for multiple) | none | +| `-optional-create-dependency` | Optional dependency for creation (can repeat flag for multiple) | none | +| `-import-dependency` | Dependency for import filter (can repeat flag for multiple) | none | +| `-interactive` | Run in interactive mode | `true` (set to `false` for scripted use) | + +### Common Gophercloud Clients + +| Service | Client Function | Module Path Prefix | +|---------|-----------------|-------------------| +| Compute | `NewComputeV2` | `github.com/gophercloud/gophercloud/v2/openstack/compute/v2/...` | +| Network | `NewNetworkV2` | `github.com/gophercloud/gophercloud/v2/openstack/networking/v2/...` | +| Block Storage | `NewBlockStorageV3` | `github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/...` | +| Identity | `NewIdentityV3` | `github.com/gophercloud/gophercloud/v2/openstack/identity/v3/...` | +| Image | `NewImageV2` | `github.com/gophercloud/gophercloud/v2/openstack/image/v2/...` | + +### Example Command (for reference only - build your own based on user input) + +```bash +# Example with dependencies - adapt based on user's answers +go run ./cmd/scaffold-controller -interactive=false \ + -kind=Port \ + -gophercloud-client=NewNetworkV2 \ + -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports \ + -required-create-dependency=Network \ + -optional-create-dependency=Subnet \ + -optional-create-dependency=SecurityGroup \ + -import-dependency=Network +``` + +After scaffolding completes, run code generation: + +```bash +make generate +``` + +Commit the scaffolding with the command used: + +```bash +git add . +git commit -m "$(cat <<'EOF' +Scaffolding for the VolumeBackup controller + +$ go run ./cmd/scaffold-controller -interactive=false \ + -kind=VolumeBackup \ + -gophercloud-client=NewBlockStorageV3 \ + -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups +EOF +)" +``` + +## Step 3: Register with Resource Generator + +Add the new resource to `cmd/resource-generator/main.go` in the `resources` slice: + +```go +var resources []templateFields = []templateFields{ + // ... existing resources (keep alphabetically sorted) ... + { + Name: "VolumeBackup", + }, +} +``` + +Then regenerate to create the `zz_generated.*.go` files: + +```bash +make generate +``` + +## Step 4: Add OpenStack Client to Scope + +Update these files in `internal/scope/`: + +### scope.go +Add interface method: +```go +NewYourResourceClient() (osclients.YourResourceClient, error) +``` + +### provider.go +Implement the constructor: +```go +func (s *providerScope) NewYourResourceClient() (osclients.YourResourceClient, error) { + return osclients.NewYourResourceClient(s.provider) +} +``` + +### mock.go +Add mock client field and implementation for testing. + +## Step 5: Register Controller + +Add to `cmd/manager/main.go`: + +```go +import ( + yourresourcecontroller "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/yourresource" +) + +// In controllers slice: +controllers := []interfaces.Controller{ + // ... + yourresourcecontroller.New(scopeFactory), +} +``` + +## Step 6: Implement TODOs + +**Reference Documentation**: For detailed patterns and rationale, see: +- `website/docs/development/controller-implementation.md` - Progressing condition, ReconcileStatus, error handling, dependencies +- `website/docs/development/api-design.md` - Filter, ResourceSpec, ResourceStatus conventions +- `website/docs/development/coding-standards.md` - Code organization, naming, logging + +Find all scaffolding TODOs: + +```bash +grep -r "TODO(scaffolding)" api/v1alpha1/ internal/controllers// +``` + +### API Types (api/v1alpha1/_types.go) + +Use the exact field names from gophercloud discovered in Step 1. + +Define: +- `ResourceSpec` - Creation parameters with validation markers +- `Filter` - Import filter with `MinProperties:=1` +- `ResourceStatus` - Observed state fields + +### Actuator (internal/controllers//actuator.go) + +Implement: +- `CreateResource()` - Build CreateOpts, call OpenStack API +- `DeleteResource()` - Call delete API +- `ListOSResourcesForImport()` - Apply filter to list results +- `ListOSResourcesForAdoption()` - Match by spec fields +- `GetResourceReconcilers()` - (if resource supports updates) + +### Implementation Patterns + +Follow the patterns in @.agents/skills/new-controller/patterns.md when implementing the actuator and API types. + +### Status Writer (internal/controllers//status.go) + +Implement: +- `ResourceAvailableStatus()` - When is resource available? +- `ApplyResourceStatus()` - Map OpenStack fields to status + +## Step 7: Write and Run Tests + +**This step is required** - do not skip it. + +Complete the test stubs in `internal/controllers//tests/` and run tests following @.agents/skills/testing/SKILL.md + +## Checklist + +- [ ] Gophercloud struct researched (field names noted) +- [ ] Similar controller reviewed (if applicable) +- [ ] Scaffolding complete +- [ ] First `make generate` run +- [ ] Scaffolding committed +- [ ] Registered in resource-generator +- [ ] Second `make generate` run (creates zz_generated files) +- [ ] OpenStack client added to scope +- [ ] Controller registered in main.go +- [ ] API types implemented: + - [ ] Correct field names (matching OpenStack conventions) + - [ ] Stricter types where appropriate (IPvAny, custom tag types) + - [ ] Status constants in types.go (if resource has provisioning states) +- [ ] Actuator methods implemented: + - [ ] DeleteResource: no cascade unless explicitly requested + - [ ] DeleteResource: handles pending states and 409 Conflict (if resource has intermediate states) + - [ ] CreateResource includes tags with sorting (if applicable) + - [ ] Proper error classification (Terminal vs retryable) + - [ ] Descriptive dependency variable names +- [ ] Status writer implemented +- [ ] Update reconciler includes tags update (if tags are mutable) +- [ ] All TODOs resolved +- [ ] `make generate` runs cleanly +- [ ] `make lint` passes +- [ ] `make test` passes +- [ ] E2E tests written (including dependency tests if applicable) +- [ ] E2E tests passing diff --git a/.agents/skills/new-controller/patterns.md b/.agents/skills/new-controller/patterns.md new file mode 100644 index 000000000..10b6e84d6 --- /dev/null +++ b/.agents/skills/new-controller/patterns.md @@ -0,0 +1,168 @@ +# ORC Controller Implementation Patterns + +Follow these principles when implementing controllers. See `website/docs/development/` for detailed rationale. + +## 1. Defensive Operations + +Avoid destructive defaults - require explicit user intent for dangerous operations. + +**Examples:** +- Never use cascade delete unless the user explicitly requests it (cascade removes all child resources) +- Don't auto-correct invalid states that might cause data loss +- Ask the user additional questions if required +- Prefer failing safely over making assumptions + +## 2. Resource Lifecycle Management + +Handle all states a resource can be in throughout its lifecycle. + +**For resources with intermediate provisioning states** (PENDING_CREATE, BUILD, PENDING_DELETE, etc.): +- Check the current state before attempting operations +- Wait for stable states before making changes +- Handle race conditions where state changes between check and action + +```go +// Example: Handle all states before deletion +switch resource.ProvisioningStatus { +case ProvisioningStatusPendingDelete: + return progress.WaitingOnOpenStack(progress.WaitingOnReady, deletingPollingPeriod) +case ProvisioningStatusPendingCreate, ProvisioningStatusPendingUpdate: + // Can't delete in pending state, wait for ACTIVE + return progress.WaitingOnOpenStack(progress.WaitingOnReady, availablePollingPeriod) +} + +// Example: Handle 409 Conflict (state changed between check and API call) +err := actuator.osClient.DeleteResource(ctx, resource.ID) +if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, deletingPollingPeriod) +} +``` + +**Note**: Resources without intermediate states (e.g., Flavor, Keypair) are created/deleted synchronously and don't need this handling. + +## 3. Deterministic State + +Ensure consistent, comparable state to enable reliable drift detection. + +**Principle**: Data should be normalized before storage and comparison so equivalent states produce identical representations. + +**Examples:** +- Sort lists before creation and comparison (tags, security group rules, allowed address pairs) +- Normalize strings (trim whitespace, consistent casing where appropriate) +- Use canonical forms for complex types + +```go +// Example: Sort tags for consistent comparison +tags := make([]string, len(resource.Tags)) +for i := range resource.Tags { + tags[i] = string(resource.Tags[i]) +} +slices.Sort(tags) +createOpts.Tags = tags + +// Example: Compare with sorting (copy before sorting to avoid mutation) +desiredTags := make([]string, len(resource.Tags)) +copy(desiredTags, resource.Tags) +slices.Sort(desiredTags) + +currentTags := make([]string, len(osResource.Tags)) +copy(currentTags, osResource.Tags) +slices.Sort(currentTags) + +if !slices.Equal(desiredTags, currentTags) { + updateOpts.Tags = &desiredTags +} +``` + +**Note**: Import `"slices"` when using sorting/comparison functions. + +## 4. Error Classification + +Distinguish between errors that can be retried vs those requiring user action. + +| Error Type | When to Use | Behavior | +|------------|-------------|----------| +| **Retryable** (default) | Transient issues (network, API unavailable) | Automatic retry with backoff | +| **Terminal** | Invalid configuration, bad input, permission denied | No retry until spec changes | + +```go +// Terminal: User must fix the spec +if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration: "+err.Error(), err) +} + +// Conflict on update: Treat as terminal (spec likely conflicts with existing state) +// unless resource has intermediate states that could cause transient conflicts +if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration updating resource: "+err.Error(), err) +} +``` + +## 5. Dependency Timing + +Resolve dependencies as late as possible, as close to the point of use as possible. + +**Rationale**: Avoid injecting dependency requirements where not strictly required. This reduces coupling and gives users greater flexibility when fixing failed deployments. + +**Examples:** +- A Subnet depends on Network for creation, but not for import by ID or deletion +- Don't require recreating a deleted Network just to delete a Subnet whose `status.ID` is already set +- Add finalizers to dependencies only immediately before the OpenStack create/update call that references them + +```go +// Good: Only fetch dependency when needed for creation +if resource.VipSubnetRef != nil { + subnet, depRS := subnetDependency.GetDependency(ctx, ...) + reconcileStatus = reconcileStatus.WithReconcileStatus(depRS) +} + +// Bad: Fetching dependency unconditionally even when not needed +subnet, depRS := subnetDependency.GetDependency(ctx, ...) // Wrong if subnet is optional +``` + +For detailed dependency implementation: @.agents/skills/add-dependency/SKILL.md + +## 6. Code Clarity + +Write self-documenting code through naming and organization. + +**Naming**: Use descriptive names that prevent ambiguity: +- `vipSubnetDependency` not `subnetDependency` (when multiple subnet types possible) +- `sourcePortDependency` vs `destinationPortDependency` +- `memberNetworkDependency` vs `externalNetworkDependency` + +**Organization**: Define constants and types where they're most accessible: +- Status constants: prefer using constants from gophercloud if available +- Only define constants in ORC's `types.go` if gophercloud doesn't provide them +- Internal helpers in `actuator.go` + +```go +// Prefer gophercloud constants when available: +import "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" +if osResource.Status == ports.StatusActive { ... } + +// Only define in types.go if gophercloud doesn't have them: +const ( + ProvisioningStatusActive = "ACTIVE" + ProvisioningStatusPendingCreate = "PENDING_CREATE" + ProvisioningStatusError = "ERROR" +) +``` + +## 7. API Safety + +Design APIs that prevent invalid states through types and validation. + +**Use stricter types** where OpenStack provides specific formats: +- `IPvAny` for IP addresses (validates format) +- `OpenStackName` for resource names - but check the specific OpenStack project for exact limits (e.g., Keystone names max 64 chars, Neutron names max 255 chars) +- Custom types with validation (e.g., tag types with length limits) + +**Note**: Always check how fields are defined in the related OpenStack project to determine correct validation constraints. + +**Add validation markers** to catch errors early: +- `+kubebuilder:validation:MinLength`, `MaxLength` +- `+kubebuilder:validation:Pattern` for format constraints +- `+kubebuilder:validation:XValidation` for cross-field rules diff --git a/.agents/skills/proposal/SKILL.md b/.agents/skills/proposal/SKILL.md new file mode 100644 index 000000000..212fadd35 --- /dev/null +++ b/.agents/skills/proposal/SKILL.md @@ -0,0 +1,251 @@ +--- +name: proposal +description: Write an enhancement proposal for a new ORC feature. Use for significant new features, breaking changes, or cross-cutting architectural changes. +disable-model-invocation: true +--- + +# Write Feature Proposal + +Guide for creating a proposal for a new feature or enhancement in ORC. + +## When to Write an Enhancement + +Write an enhancement proposal when you want to: +- Add a significant new feature or capability +- Make breaking changes to existing APIs +- Deprecate or remove functionality +- Make cross-cutting architectural changes +- Change behavior that users depend on + +You do **not** need an enhancement for: +- Bug fixes +- Small improvements or refactoring +- Documentation updates +- Adding support for additional OpenStack resource fields +- Test improvements + +When in doubt, suggest opening a GitHub issue first to discuss whether an enhancement proposal is needed. + +## Enhancement Lifecycle + +Enhancements move through the following statuses: + +| Status | Description | +|--------|-------------| +| `implementable` | The enhancement has been approved and is ready for implementation | +| `implemented` | The enhancement has been fully implemented and merged | +| `withdrawn` | The enhancement is no longer being pursued | + +## Template and File Location + +Use the enhancement template at `enhancements/TEMPLATE.md`. + +For full process details, see `enhancements/README.md`. + +### Creating the Proposal File + +Simple enhancement (single file): +```bash +cp enhancements/TEMPLATE.md enhancements/your-feature-name.md +``` + +Enhancement with supporting files (images, diagrams): +```bash +mkdir enhancements/your-feature-name +cp enhancements/TEMPLATE.md enhancements/your-feature-name/your-feature-name.md +``` + +## Information to Gather from User + +Before writing a proposal, ask the user about: + +1. **Feature Overview** + - What OpenStack resource or capability does this involve? + - What problem does this solve for users? + - Is this a new controller, enhancement to existing controller, or infrastructure change? + +2. **Use Cases** + - Who will use this feature? (end users, operators, other controllers) + - What are the primary use cases? + - Are there edge cases to consider? + +3. **Dependencies** + - Does this depend on other ORC resources? + - Does this require new OpenStack API capabilities? + - Are there upstream dependencies (gophercloud, controller-runtime)? + +4. **Scope** + - Is this a minimal viable feature or full implementation? + - Are there phases or milestones to break this into? + - What's explicitly out of scope? + +5. **Testing** + - How is this going to be tested? + - Are there specific E2E test scenarios required? + - What OpenStack capabilities are needed for testing? + +6. **Existing Infrastructure** (for non-controller enhancements) + - What related functionality already exists in ORC? + - Are there existing endpoints, ports, or configurations to integrate with? + - What frameworks/libraries does ORC already use for this area? + +## Research Phase + +Before writing the proposal, research relevant areas based on the enhancement type: + +### For Controller Enhancements + +1. **OpenStack API** + - Read the OpenStack API documentation for the resource + - Identify required vs optional fields + - Understand resource lifecycle (creation, updates, deletion) + - Check for async operations (polling requirements) + +2. **Gophercloud Support** + - Check if gophercloud has client support for this resource + - Identify the module path and types + - Note any missing functionality that needs upstream work + +3. **Existing Patterns** + - Look at similar controllers in ORC for patterns to follow + - Identify if existing utilities can be reused + - Check if new generic functionality is needed + +4. **Dependencies** + - Map out all ORC resource dependencies + - Determine which are required vs optional + - Identify deletion guard requirements + +### For Infrastructure Enhancements (metrics, webhooks, etc.) + +1. **Current Implementation** + - Check existing code for related functionality (e.g., `cmd/manager/`, `internal/`) + - Identify current ports, endpoints, and configurations + - Verify technical details by reading the actual code + +2. **Framework Capabilities** + - Check controller-runtime documentation for built-in features + - Identify what's provided vs what needs custom implementation + +3. **Integration Points** + - How does this integrate with existing infrastructure? + - What configuration already exists that this should use? + +## Filling Out the Template + +Read the template at `enhancements/TEMPLATE.md` and fill in each section: + +| Section | What to Include | +|---------|-----------------| +| **Metadata table** | Status (`implementable`), author, dates, tracking issue (TBD initially) | +| **Summary** | 1-2 paragraph overview of the enhancement | +| **Motivation** | Why this is needed, who benefits, links to issues | +| **Goals** | Specific, measurable objectives | +| **Non-Goals** | What's explicitly out of scope | +| **Proposal** | Detailed solution with API examples | +| **Risks and Edge Cases** | What could go wrong, mitigations (see risk checklist below) | +| **Alternatives Considered** | Other approaches and why rejected | +| **Implementation History** | Timeline of major milestones | + +### For New Controller Proposals + +**Note**: New controllers following existing patterns typically don't need an enhancement proposal. Only write a proposal if the controller requires new patterns or architectural changes. + +If a proposal is needed, in the **Proposal** section, include: + +```yaml +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ResourceName +metadata: + name: example +spec: + cloudCredentialsRef: + secretName: openstack-credentials + cloudName: openstack + resource: + # Required fields with descriptions + # Optional fields with descriptions + import: + filter: + # Filter fields +status: + id: "uuid" + conditions: [...] + resource: + # Observed state fields +``` + +Also describe: +- Controller behavior (creation, updates, deletion) +- Dependencies (required vs optional, deletion guards) +- Mutable vs immutable fields + +### Risk Checklist + +Address each of these in the **Risks and Edge Cases** section: + +| Risk Category | Questions to Answer | +|---------------|---------------------| +| **API compatibility** | Will this break existing users? Are metric/label names stable? | +| **Security** | Are there security implications? (Often N/A for read-only features) | +| **Performance** | Could this impact controller performance at scale? | +| **Error handling** | What happens when things fail? | +| **Upgrade/downgrade** | How does this affect users upgrading or downgrading ORC? | +| **OpenStack compatibility** | Does this work across different OpenStack versions? (N/A for K8s-only) | +| **Interaction with existing features** | Could this conflict with existing behavior? | + +### Verification Before Submission + +Before finalizing the proposal: + +1. **Internal consistency**: Verify anything referenced in one section is defined elsewhere + - If you mention a metric/API/config in mitigations, ensure it's defined in Proposal + - If you reference a flag, show its usage + +2. **Technical accuracy**: Verify details against actual code + - Check ports, endpoints, paths in the codebase + - Verify framework capabilities match what you describe + +3. **Completeness**: Ensure examples are complete and correct + - Code examples should compile conceptually + - Config examples should be valid YAML/JSON + +## Tips for Writing Good Enhancements + +1. **Be concise but complete** - Include enough detail for reviewers to understand the proposal without unnecessary verbosity. + +2. **Focus on the "why"** - Motivation is often more important than implementation details. + +3. **Think about edge cases** - The Risks and Edge Cases section is where you demonstrate you've thought through the implications. + +4. **Consider alternatives** - Showing that you've evaluated other approaches strengthens your proposal. + +5. **Keep it updated** - As implementation progresses, update the Implementation History section. + +## Submission Process + +1. **Fill out the template** with proposal details +2. **Open a pull request** with title: `Enhancement: Add support for feature X` +3. **Iterate based on feedback** - Discussion happens on the PR +4. **Create a tracking issue** once merged - Label with `enhancement` and link in metadata + +## Review Process + +- Any community member can propose an enhancement +- Maintainers review proposals and provide feedback on the PR +- Enhancements are approved using lazy consensus (typically one week review period) +- The enhancement author is typically expected to drive implementation + +## Checklist + +- [ ] Confirmed enhancement proposal is needed (not just a bug fix or small improvement) +- [ ] Gathered feature requirements from user +- [ ] Researched relevant areas (OpenStack API, gophercloud, or existing infrastructure) +- [ ] Reviewed similar implementations in ORC +- [ ] Copied template to `enhancements/` +- [ ] Filled in all template sections +- [ ] Addressed all items in risk checklist +- [ ] Documented alternatives considered +- [ ] Verified internal consistency (references match definitions) +- [ ] Verified technical accuracy against codebase +- [ ] Opened PR for review diff --git a/.agents/skills/testing/SKILL.md b/.agents/skills/testing/SKILL.md new file mode 100644 index 000000000..88919c1f7 --- /dev/null +++ b/.agents/skills/testing/SKILL.md @@ -0,0 +1,107 @@ +--- +name: testing +description: Run ORC tests (unit tests, linting, and E2E tests). Use after making changes to verify correctness. +disable-model-invocation: true +--- + +# ORC Testing Guide + +Run unit tests, linting, and E2E tests for ORC controllers. + +## Unit Tests and Linting + +Before running E2E tests, ensure code compiles and passes linting: + +```bash +make generate +make lint +make test +``` + +## E2E Test Prerequisites + +E2E tests require `E2E_OSCLOUDS` environment variable pointing to a `clouds.yaml` file containing: +- A cloud named `openstack` - regular user credentials +- A cloud named `devstack-admin` - admin credentials + +If the user did not provide `E2E_OSCLOUDS`, tell them local E2E testing will be skipped and they should run it manually later or in CI. + +## Running E2E Tests + +If `E2E_OSCLOUDS` is provided, execute each step in order: + +**Step 1: Create kind cluster (if not already running)** +```bash +# Check if cluster exists +kind get clusters + +# Create only if no cluster exists +kind create cluster +``` +If a cluster already exists, skip creation and proceed to Step 2. + +**Step 2: Verify cluster is ready** +```bash +kubectl get nodes +``` +Ensure node shows `Ready` status. + +**Step 3: Install CRDs** +```bash +kubectl apply -k config/crd --server-side +``` + +**Step 4: Stop any existing manager, rebuild, and start** +```bash +# Stop any existing manager to ensure we're running latest code +pkill -f orc-manager || true + +# Build and start fresh +go build -o /tmp/orc-manager ./cmd/manager +/tmp/orc-manager -zap-log-level 5 > /tmp/manager.log 2>&1 & +``` + +**Step 5: Wait for manager to start and verify it's running** +```bash +sleep 5 +ps aux | grep "[o]rc-manager" +``` +If no process found, check `/tmp/manager.log` for errors. + +**Step 6: Run E2E tests** +Replace `/path/to/clouds.yaml` with the actual path and `` with the controller name: +```bash +E2E_OSCLOUDS=/path/to/clouds.yaml E2E_KUTTL_DIR=internal/controllers//tests make test-e2e +``` + +**Step 7: If tests fail, review manager logs** +```bash +# Search for errors first (logs are verbose at level 5) +grep -i error /tmp/manager.log | tail -50 + +# Or view more context +tail -500 /tmp/manager.log +``` +Use these logs to diagnose and fix issues, then re-run the tests. + +**Step 8: Cleanup** +After tests pass (or when done debugging): +```bash +pkill -f "orc-manager" || true +kind delete cluster +rm -f /tmp/manager.log /tmp/orc-manager +``` + +## E2E Test Directory Structure + +Tests are located in `internal/controllers//tests/`: + +| Directory | Purpose | +|-----------|---------| +| `-create-minimal/` | Create with minimum required fields | +| `-create-full/` | Create with all fields | +| `-import/` | Import existing resource | +| `-import-error/` | Import with no matches | +| `-dependency/` | Test dependency waiting and deletion guards | +| `-import-dependency/` | Test import with dependency references | +| `-update/` | Test mutable field updates | diff --git a/.agents/skills/update-controller/SKILL.md b/.agents/skills/update-controller/SKILL.md new file mode 100644 index 000000000..4389af882 --- /dev/null +++ b/.agents/skills/update-controller/SKILL.md @@ -0,0 +1,249 @@ +--- +name: update-controller +description: Update an existing ORC controller. Use when adding fields, making fields mutable, adding tag support, or improving error handling. +disable-model-invocation: true +--- + +# Update Existing Controller + +Guide for modifying an existing ORC controller. + +**Reference**: See `website/docs/development/` for detailed patterns and rationale. + +## Before Making Changes + +Research the resource before implementing changes: + +1. **Check gophercloud** for the resource's API: + ```bash + go doc .UpdateOpts + go doc .CreateOpts + ``` + +2. **Check existing controller** patterns: + - How are similar fields handled? + - Does the resource have intermediate provisioning states? + - How are tags updated (standard Update API or separate tags API)? + +3. **Check OpenStack API documentation** for: + - Field constraints (max lengths, allowed values) + - Mutability (can the field be updated after creation?) + +## Key Principles + +When updating controllers, follow the patterns in @.agents/skills/new-controller/patterns.md + +## Common Update Scenarios + +### Adding a New Field to Spec + +1. **Update API types** in `api/v1alpha1/_types.go`: + - Add field to `ResourceSpec` + - Add corresponding field to `ResourceStatus` + - Add validation markers (`+kubebuilder:validation:*`) + +2. **Update actuator** in `internal/controllers//actuator.go`: + - Add field to `CreateOpts` in `CreateResource()` + - If mutable, add update logic in reconciler + +3. **Update status writer** in `internal/controllers//status.go`: + - Add field mapping in `ApplyResourceStatus()` + +4. **Regenerate**: + ```bash + make generate + ``` + +5. **Update tests** to cover the new field (add only what's relevant to your change): + - Unit tests in `internal/controllers//actuator_test.go` (if complex logic) + - E2E tests in `internal/controllers//tests/`: + - `create-full`: Set new field to non-default value and verify + - `create-minimal`: Verify default value behavior (if field has defaults) + - `update`: Test setting and unsetting the field (only if field is mutable) + - `*-dependency`: Test dependency behavior (only if adding a new dependency) + - `*import*`: Test import filtering (only if adding a new filter field) + +### Adding a New Filter Field + +1. Add field to `Filter` in `api/v1alpha1/_types.go` + +2. Update `ListOSResourcesForImport()` in actuator to apply the filter + +3. Add import test case + +### Making a Field Mutable + +1. Remove immutability validation from the field: + ```go + // Remove or update this validation + // +kubebuilder:validation:XValidation:rule="self == oldSelf" + ``` + +2. Implement `GetResourceReconcilers()` if not already present + +3. Add update handling to the `updateResource()` reconciler (or create it if not present): + ```go + func (actuator myActuator) updateResource(...) progress.ReconcileStatus { + var updateOpts resources.UpdateOpts + // Add a handleXXXUpdate() call for each mutable field + handleMyFieldUpdate(&updateOpts, resource, osResource) + // Call API only if something changed + if updateOpts != (resources.UpdateOpts{}) { + _, err := actuator.osClient.UpdateResource(ctx, *obj.Status.ID, updateOpts) + // ... + } + } + + func handleMyFieldUpdate(updateOpts *resources.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + if resource.MyField != nil && *resource.MyField != osResource.MyField { + updateOpts.MyField = resource.MyField + } + } + ``` + + **Note**: Only create a separate reconciler method if the field requires a different API call (e.g., tags on networking resources use a separate tags API). + +4. Register in `GetResourceReconcilers()`: + ```go + return []resourceReconciler{ + actuator.updateResource, + }, nil + ``` + +### Adding a Dependency + +See @.agents/skills/add-dependency/SKILL.md for detailed steps. + +### Improving DeleteResource + +For resources with intermediate provisioning states, ensure robust deletion: + +```go +func (actuator myActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + // Handle intermediate states + switch resource.ProvisioningStatus { + case ProvisioningStatusPendingDelete: + return progress.WaitingOnOpenStack(progress.WaitingOnReady, deletingPollingPeriod) + case ProvisioningStatusPendingCreate, ProvisioningStatusPendingUpdate: + // Can't delete in pending state, wait for ACTIVE + return progress.WaitingOnOpenStack(progress.WaitingOnReady, availablePollingPeriod) + } + + err := actuator.osClient.DeleteResource(ctx, resource.ID) + // Handle 409 (state changed between check and API call) + if orcerrors.IsConflict(err) { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, deletingPollingPeriod) + } + return progress.WrapError(err) +} +``` + +**Important**: Never use cascade delete unless explicitly requested by the user. + +### Adding Tag Support + +**Note**: Tag handling varies by OpenStack service. Some services (e.g., block storage) include tags in the standard Update API, while others (e.g., networking) require a separate tags API and a dedicated reconciler. Check gophercloud for the specific resource. + +1. Add `Tags` field to spec and status: + ```go + // In ResourceSpec + // +kubebuilder:validation:MaxItems:=64 + // +listType=set + Tags []NeutronTag `json:"tags,omitempty"` + + // In ResourceStatus + // +listType=atomic + Tags []string `json:"tags,omitempty"` + ``` + +2. Sort tags before creation (deterministic state): + ```go + tags := make([]string, len(resource.Tags)) + for i := range resource.Tags { + tags[i] = string(resource.Tags[i]) + } + slices.Sort(tags) + createOpts.Tags = tags + ``` + +3. Add tag update handler with sorting: + ```go + func handleTagsUpdate(updateOpts *resources.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + desiredTags := make([]string, len(resource.Tags)) + for i := range resource.Tags { + desiredTags[i] = string(resource.Tags[i]) + } + slices.Sort(desiredTags) + + currentTags := make([]string, len(osResource.Tags)) + copy(currentTags, osResource.Tags) // Don't mutate original + slices.Sort(currentTags) + + if !slices.Equal(desiredTags, currentTags) { + updateOpts.Tags = &desiredTags + } + } + ``` + +4. Register in `GetResourceReconcilers()`: + ```go + return []resourceReconciler{ + actuator.updateResource, // includes handleTagsUpdate + }, nil + ``` + +**Note**: Import `"slices"` for sorting/comparison functions. + +### Adding Status Constants + +For resources with provisioning states, prefer using constants from gophercloud when available. Only define constants in ORC's `types.go` if gophercloud doesn't provide them. + +```go +// Prefer gophercloud constants when available: +import "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/loadbalancers" +if osResource.ProvisioningStatus == loadbalancers.ProvisioningStatusActive { ... } + +// Only define in types.go if gophercloud doesn't have them: +const ( + MyResourceProvisioningStatusActive = "ACTIVE" + MyResourceProvisioningStatusPendingCreate = "PENDING_CREATE" + MyResourceProvisioningStatusError = "ERROR" +) +``` + +See also `@.agents/skills/new-controller/patterns.md` for more details on this pattern. + +### Improving Error Handling + +Ensure proper error classification: + +```go +// Terminal: Invalid configuration - user must fix spec +if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration: "+err.Error(), err) +} +return nil, progress.WrapError(err) + +// Conflict on update: Treat as terminal +if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration updating resource: "+err.Error(), err) +} +``` + +## Testing Changes + +Follow @.agents/skills/testing/SKILL.md for running unit tests, linting, and E2E tests. + +## Checklist + +- [ ] API types updated with proper validation +- [ ] Actuator updated (create/update logic) +- [ ] Status writer updated +- [ ] `make generate` runs cleanly +- [ ] `make lint` passes +- [ ] `make test` passes +- [ ] E2E tests updated/added +- [ ] E2E tests passing +- [ ] Unit tests added (if complex logic) diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..e27921b0b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(go mod tidy:*)", + "Bash(make generate:*)", + "Bash(go build:*)", + "Bash(go fmt:*)", + "Bash(go doc:*)", + "Bash(make test:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 000000000..2b7a412b8 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..d64084d55 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,264 @@ +# OpenStack Resource Controller (ORC) - Development Guide + +This document provides instructions for AI agents to develop controllers in the ORC project. + +## Project Overview + +ORC is a Kubernetes operator that manages OpenStack resources declaratively. Each OpenStack resource (Flavor, Server, Network, etc.) has a corresponding Kubernetes Custom Resource and controller. + +**Key Principle**: ORC objects only reference other ORC objects, never OpenStack resources directly. OpenStack resource IDs appear only in status fields. + +## Project Structure + +``` +openstack-resource-controller/ +├── api/v1alpha1/ # CRD type definitions (*_types.go) +├── internal/ +│ ├── controllers/ # Controller implementations +│ │ └── / # Each controller in its own package +│ │ ├── controller.go # Setup, dependencies, SetupWithManager +│ │ ├── actuator.go # OpenStack CRUD operations +│ │ ├── status.go # Status writer implementation +│ │ ├── zz_generated.*.go # Generated code (DO NOT EDIT) +│ │ └── tests/ # KUTTL E2E tests +│ ├── osclients/ # OpenStack API client wrappers +│ ├── scope/ # Cloud credentials & client factory +│ └── util/ # Utilities (errors, dependency, tags) +├── cmd/ +│ ├── manager/ # Main entry point +│ ├── resource-generator/ # Code generation +│ └── scaffold-controller/ # New controller scaffolding +└── website/docs/development/ # Detailed documentation +``` + +## Architecture + +### Generic Reconciler Framework + +All controllers use a generic reconciler that handles the reconciliation loop. Controllers implement interfaces: + +- **CreateResourceActuator**: Create and import operations +- **DeleteResourceActuator**: Delete operations +- **ReconcileResourceActuator**: Post-creation updates (optional) +- **ResourceStatusWriter**: Status and condition management + +### Key Interfaces + +Controllers implement these methods (see `internal/controllers/flavor/` for a simple example): + +```go +// Required by all actuators +GetResourceID(osResource) string +GetOSResourceByID(ctx, id) (*osResource, ReconcileStatus) +ListOSResourcesForAdoption(ctx, obj) (iterator, bool) + +// For creation/import +ListOSResourcesForImport(ctx, obj, filter) (iterator, ReconcileStatus) +CreateResource(ctx, orcObject) (*osResource, ReconcileStatus) + +// For deletion +DeleteResource(ctx, orcObject, osResource) ReconcileStatus + +// Optional - for updates after creation +GetResourceReconcilers(ctx, obj, osResource) ([]ResourceReconciler, error) +``` + +### Two Critical Conditions + +Every ORC object has these conditions: + +1. **Progressing** + - `True`: Spec doesn't match status; controller expects more reconciles + - `False`: Either available OR terminal error (no more reconciles until spec changes) + +2. **Available** + - `True`: Resource is ready for use + - Determined by `ResourceStatusWriter.ResourceAvailableStatus()` + +### ReconcileStatus Pattern + +Methods return `ReconcileStatus` instead of `error`: + +```go +nil // Success, no reschedule +progress.WrapError(err) // Wrap error for handling +reconcileStatus.WithRequeue(5*time.Second) // Schedule reconcile after delay +reconcileStatus.WithProgressMessage("waiting...") // Add progress message +``` + +### Error Classification + +- **Transient errors** (5xx, API unavailable): Default handling with exponential backoff +- **Terminal errors** (400, invalid config): Wrap with `orcerrors.Terminal()` - no retry + +```go +// Terminal error example +if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration: "+err.Error(), err) +} +return nil, progress.WrapError(err) +``` + +## Dependencies + +Dependencies are core to ORC - they ensure resources are created in order. + +### Types of Dependencies + +1. **Normal Dependency**: Wait for object to exist and be available +2. **Deletion Guard Dependency**: Normal + prevents deletion of dependency while in use + +### Declaring Dependencies (in controller.go) + +```go +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.SecurityGroupList, *orcv1alpha1.Project]( + "spec.resource.projectRef", // Field path for indexing + func(sg *orcv1alpha1.SecurityGroup) []string { + if sg.Spec.Resource != nil && sg.Spec.Resource.ProjectRef != nil { + return []string{string(*sg.Spec.Resource.ProjectRef)} + } + return nil + }, + finalizer, externalObjectFieldOwner, +) +``` + +### Using Dependencies (in actuator.go) + +```go +project, reconcileStatus := projectDependency.GetDependency( + ctx, actuator.k8sClient, orcObject, + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, +) +if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus +} +// project is now guaranteed available +projectID := ptr.Deref(project.Status.ID, "") +``` + +## Common Patterns + +### Resource Name Helper + +```go +func getResourceName(orcObject *orcv1alpha1.Flavor) string { + if orcObject.Spec.Resource.Name != nil { + return *orcObject.Spec.Resource.Name + } + return orcObject.Name +} +``` + +### Type Aliases (top of actuator.go) + +```go +type ( + osResourceT = flavors.Flavor + createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) +``` + +### Interface Assertions + +```go +var _ createResourceActuator = flavorActuator{} +var _ deleteResourceActuator = flavorActuator{} +``` + +### Pointer Handling + +```go +import "k8s.io/utils/ptr" + +ptr.Deref(optionalPtr, defaultValue) // Dereference with default +ptr.To(value) // Create pointer +``` + +## API Types Structure + +### ResourceSpec (creation parameters) + +```go +// Most resources have a mix of immutable and mutable fields. +// Immutability is typically applied per-field, not on the whole struct. +type ServerResourceSpec struct { + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable" + ImageRef KubernetesNameRef `json:"imageRef,omitempty"` + + // tags is mutable (no immutability validation) + // +optional + Tags []ServerTag `json:"tags,omitempty"` +} + +// Some resources are fully immutable (rare - e.g., Flavor, ServerGroup) +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="FlavorResourceSpec is immutable" +type FlavorResourceSpec struct { + // ... +} +``` + +### Filter (import parameters) + +```go +// +kubebuilder:validation:MinProperties:=1 +type FlavorFilter struct { + Name *OpenStackName `json:"name,omitempty"` + RAM *int32 `json:"ram,omitempty"` +} +``` + +### ResourceStatus (observed state) + +```go +type FlavorResourceStatus struct { + Name string `json:"name,omitempty"` + RAM *int32 `json:"ram,omitempty"` +} +``` + +## Logging Levels + +```go +import "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + +log.V(logging.Status).Info("...") // Always shown: startup, shutdown +log.V(logging.Info).Info("...") // Default: creation/deletion, reconcile complete +log.V(logging.Verbose).Info("...") // Admin: fires every reconcile +log.V(logging.Debug).Info("...") // Development: detailed debugging +``` + +## Key Make Targets + +```bash +make generate # Generate all code (run after API type changes) +make build # Build manager binary +make lint # Run linters +make test # Run unit tests +make test-e2e # Run KUTTL E2E tests (requires E2E_OSCLOUDS) +make fmt # Format code +``` + +## Reference Controllers + +- **Simple**: `internal/controllers/flavor/` - No dependencies, immutable +- **With dependencies**: `internal/controllers/securitygroup/` - Project dependency, rules reconciliation +- **Complex**: `internal/controllers/server/` - Multiple dependencies, reconcilers + +## Documentation + +Detailed documentation in `website/docs/development/`: +- `scaffolding.md` - Creating new controllers +- `controller-implementation.md` - Progressing condition, ReconcileStatus +- `interfaces.md` - Detailed interface descriptions +- `coding-standards.md` - Code style and conventions +- `writing-tests.md` - Testing patterns diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 7693f2ab54376428c6f1d3c2710edbd584898771 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Thu, 19 Feb 2026 16:05:34 +0000 Subject: [PATCH 055/121] Scaffolding the AddressScope controller go run ./cmd/scaffold-controller \ -interactive=false \ -kind AddressScope \ -gophercloud-client NewNetworkV2 \ -gophercloud-module github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/addressscopes \ -optional-create-dependency Project \ -import-dependency Project --- api/v1alpha1/addressscope_types.go | 88 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 75 +++++ cmd/models-schema/zz_generated.openapi.go | 105 +++++++ config/rbac/role.yaml | 2 + .../openstack_v1alpha1_addressscope.yaml | 14 + internal/controllers/addressscope/actuator.go | 273 ++++++++++++++++++ .../controllers/addressscope/actuator_test.go | 119 ++++++++ .../controllers/addressscope/controller.go | 114 ++++++++ internal/controllers/addressscope/status.go | 64 ++++ .../addressscope-create-full/00-assert.yaml | 33 +++ .../00-create-resource.yaml | 29 ++ .../addressscope-create-full/00-secret.yaml | 6 + .../tests/addressscope-create-full/README.md | 11 + .../00-assert.yaml | 27 ++ .../00-create-resource.yaml | 14 + .../00-secret.yaml | 6 + .../01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../addressscope-create-minimal/README.md | 15 + .../addressscope-dependency/00-assert.yaml | 30 ++ .../00-create-resources-missing-deps.yaml | 27 ++ .../addressscope-dependency/00-secret.yaml | 6 + .../addressscope-dependency/01-assert.yaml | 30 ++ .../01-create-dependencies.yaml | 19 ++ .../addressscope-dependency/02-assert.yaml | 17 ++ .../02-delete-dependencies.yaml | 9 + .../addressscope-dependency/03-assert.yaml | 9 + .../03-delete-resources.yaml | 10 + .../tests/addressscope-dependency/README.md | 21 ++ .../00-assert.yaml | 17 ++ .../00-import-resource.yaml | 26 ++ .../00-secret.yaml | 6 + .../01-assert.yaml | 32 ++ .../01-create-trap-resource.yaml | 28 ++ .../02-assert.yaml | 34 +++ .../02-create-resource.yaml | 27 ++ .../03-assert.yaml | 6 + .../03-delete-import-dependencies.yaml | 7 + .../04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../addressscope-import-dependency/README.md | 29 ++ .../addressscope-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 28 ++ .../addressscope-import-error/00-secret.yaml | 6 + .../addressscope-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/addressscope-import-error/README.md | 13 + .../tests/addressscope-import/00-assert.yaml | 15 + .../00-import-resource.yaml | 15 + .../tests/addressscope-import/00-secret.yaml | 6 + .../tests/addressscope-import/01-assert.yaml | 34 +++ .../01-create-trap-resource.yaml | 17 ++ .../tests/addressscope-import/02-assert.yaml | 33 +++ .../02-create-resource.yaml | 14 + .../tests/addressscope-import/README.md | 18 ++ .../tests/addressscope-update/00-assert.yaml | 26 ++ .../00-minimal-resource.yaml | 14 + .../tests/addressscope-update/00-secret.yaml | 6 + .../tests/addressscope-update/01-assert.yaml | 17 ++ .../01-updated-resource.yaml | 10 + .../tests/addressscope-update/02-assert.yaml | 26 ++ .../02-reverted-resource.yaml | 7 + .../tests/addressscope-update/README.md | 17 ++ internal/osclients/addressscope.go | 104 +++++++ website/docs/crd-reference.md | 10 + 65 files changed, 1910 insertions(+) create mode 100644 api/v1alpha1/addressscope_types.go create mode 100644 config/samples/openstack_v1alpha1_addressscope.yaml create mode 100644 internal/controllers/addressscope/actuator.go create mode 100644 internal/controllers/addressscope/actuator_test.go create mode 100644 internal/controllers/addressscope/controller.go create mode 100644 internal/controllers/addressscope/status.go create mode 100644 internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-full/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-full/README.md create mode 100644 internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-minimal/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-minimal/01-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-create-minimal/README.md create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/01-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/02-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/03-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-dependency/README.md create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/01-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/02-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/03-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/04-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-dependency/README.md create mode 100644 internal/controllers/addressscope/tests/addressscope-import-error/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-error/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-error/01-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import-error/README.md create mode 100644 internal/controllers/addressscope/tests/addressscope-import/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-import/README.md create mode 100644 internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/00-secret.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/02-reverted-resource.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/README.md create mode 100644 internal/osclients/addressscope.go diff --git a/api/v1alpha1/addressscope_types.go b/api/v1alpha1/addressscope_types.go new file mode 100644 index 000000000..d7f47e804 --- /dev/null +++ b/api/v1alpha1/addressscope_types.go @@ -0,0 +1,88 @@ +/* +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 + +// AddressScopeResourceSpec contains the desired state of the resource. +type AddressScopeResourceSpec 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"` + + // 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/layer3/addressscopes + // + // 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"` +} + +// AddressScopeFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type AddressScopeFilter 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"` + + // 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/layer3/addressscopes +} + +// AddressScopeResourceStatus represents the observed state of the resource. +type AddressScopeResourceStatus 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"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the AddressScope structure from + // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/addressscopes +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3f9a9f21f..bc18dc6d0 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -45,6 +45,81 @@ func (in *Address) DeepCopy() *Address { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeFilter) DeepCopyInto(out *AddressScopeFilter) { + *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 + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeFilter. +func (in *AddressScopeFilter) DeepCopy() *AddressScopeFilter { + if in == nil { + return nil + } + out := new(AddressScopeFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeResourceSpec) DeepCopyInto(out *AddressScopeResourceSpec) { + *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 + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeResourceSpec. +func (in *AddressScopeResourceSpec) DeepCopy() *AddressScopeResourceSpec { + if in == nil { + return nil + } + out := new(AddressScopeResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeResourceStatus) DeepCopyInto(out *AddressScopeResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeResourceStatus. +func (in *AddressScopeResourceStatus) DeepCopy() *AddressScopeResourceStatus { + if in == nil { + return nil + } + out := new(AddressScopeResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AllocationPool) DeepCopyInto(out *AllocationPool) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index d00ee16e4..81b3df785 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -31,6 +31,9 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Address": schema_openstack_resource_controller_v2_api_v1alpha1_Address(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllocationPool": schema_openstack_resource_controller_v2_api_v1alpha1_AllocationPool(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllocationPoolStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllocationPoolStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPair(ref), @@ -561,6 +564,108 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Address(ref common.Ref } } +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeFilter 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: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeResourceSpec 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: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeResourceStatus 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: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_AllocationPool(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 1bb68f2b9..c7aac3f35 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -17,6 +17,7 @@ rules: - apiGroups: - openstack.k-orc.cloud resources: + - addressscopes - domains - endpoints - flavors @@ -49,6 +50,7 @@ rules: - apiGroups: - openstack.k-orc.cloud resources: + - addressscopes/status - domains/status - endpoints/status - flavors/status diff --git a/config/samples/openstack_v1alpha1_addressscope.yaml b/config/samples/openstack_v1alpha1_addressscope.yaml new file mode 100644 index 000000000..9647f7d75 --- /dev/null +++ b/config/samples/openstack_v1alpha1_addressscope.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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 AddressScope + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/addressscope/actuator.go b/internal/controllers/addressscope/actuator.go new file mode 100644 index 000000000..8504f474d --- /dev/null +++ b/internal/controllers/addressscope/actuator.go @@ -0,0 +1,273 @@ +/* +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 addressscope + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/addressscopes" + 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 = addressscopes.AddressScope + + 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 addressscopeActuator struct { + osClient osclients.AddressScopeClient + k8sClient client.Client +} + +var _ createResourceActuator = addressscopeActuator{} +var _ deleteResourceActuator = addressscopeActuator{} + +func (addressscopeActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator addressscopeActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetAddressScope(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator addressscopeActuator) 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 := addressscopes.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListAddressScopes(ctx, listOpts), true +} + +func (actuator addressscopeActuator) 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( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := addressscopes.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + ProjectID: ptr.Deref(project.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListAddressScopes(ctx, listOpts), reconcileStatus +} + +func (actuator addressscopeActuator) 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, func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) + if project != nil { + projectID = ptr.Deref(project.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := addressscopes.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + ProjectID: projectID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateAddressScope(ctx, createOpts) + if err != nil { + // We should require the spec to be updated before retrying a create which returned a conflict + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + return osResource, nil +} + +func (actuator addressscopeActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteAddressScope(ctx, resource.ID)) +} + +func (actuator addressscopeActuator) 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 := addressscopes.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.UpdateAddressScope(ctx, osResource.ID, updateOpts) + + // We should require the spec to be updated before retrying an update which returned a conflict + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts addressscopes.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToAddressScopeUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["address_scope"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *addressscopes.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *addressscopes.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator addressscopeActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type addressscopeHelperFactory struct{} + +var _ helperFactory = addressscopeHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.AddressScope, controller interfaces.ResourceController) (addressscopeActuator, 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 addressscopeActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return addressscopeActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewAddressScopeClient() + if err != nil { + return addressscopeActuator{}, progress.WrapError(err) + } + + return addressscopeActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (addressscopeHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return addressscopeAdapter{obj} +} + +func (addressscopeHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (addressscopeHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/addressscope/actuator_test.go b/internal/controllers/addressscope/actuator_test.go new file mode 100644 index 000000000..dd3cabaab --- /dev/null +++ b/internal/controllers/addressscope/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 addressscope + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/addressscopes" + 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 addressscopes.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: addressscopes.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: addressscopes.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.AddressScope{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.AddressScopeSpec{ + Resource: &orcv1alpha1.AddressScopeResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := addressscopes.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.AddressScopeResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := addressscopes.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/addressscope/controller.go b/internal/controllers/addressscope/controller.go new file mode 100644 index 000000000..daa8694e6 --- /dev/null +++ b/internal/controllers/addressscope/controller.go @@ -0,0 +1,114 @@ +/* +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 addressscope + +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 = "addressscope" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=addressscopes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=addressscopes/status,verbs=get;update;patch + +type addressscopeReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return addressscopeReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (addressscopeReconcilerConstructor) GetName() string { + return controllerName +} + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.AddressScopeList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(addressscope *orcv1alpha1.AddressScope) []string { + resource := addressscope.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.AddressScopeList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(addressscope *orcv1alpha1.AddressScope) []string { + resource := addressscope.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c addressscopeReconcilerConstructor) 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 + } + + projectImportWatchEventHandler, err := projectImportDependency.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{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + For(&orcv1alpha1.AddressScope{}) + + if err := errors.Join( + projectDependency.AddToManager(ctx, mgr), + projectImportDependency.AddToManager(ctx, mgr), + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, addressscopeHelperFactory{}, addressscopeStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/addressscope/status.go b/internal/controllers/addressscope/status.go new file mode 100644 index 000000000..d64cc6520 --- /dev/null +++ b/internal/controllers/addressscope/status.go @@ -0,0 +1,64 @@ +/* +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 addressscope + +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 addressscopeStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.AddressScopeApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.AddressScopeStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.AddressScope, *osResourceT, *objectApplyT, *statusApplyT] = addressscopeStatusWriter{} + +func (addressscopeStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.AddressScope(name, namespace) +} + +func (addressscopeStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.AddressScope, 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 (addressscopeStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.AddressScopeResourceStatus(). + WithProjectID(osResource.ProjectID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the AddressScopeResourceStatus 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/addressscope/tests/addressscope-create-full/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml new file mode 100644 index 000000000..752cf81ca --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-create-full +status: + resource: + name: addressscope-create-full-override + description: AddressScope 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: AddressScope + name: addressscope-create-full + ref: addressscope + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: addressscope-create-full + ref: project +assertAll: + - celExpr: "addressscope.status.id != ''" + - celExpr: "addressscope.status.resource.projectID == project.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml new file mode 100644 index 000000000..93adb07a2 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: addressscope-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: addressscope-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: addressscope-create-full-override + description: AddressScope from "create full" test + projectRef: addressscope-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/addressscope/tests/addressscope-create-full/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-create-full/README.md b/internal/controllers/addressscope/tests/addressscope-create-full/README.md new file mode 100644 index 000000000..2dcbd470d --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-create-full/README.md @@ -0,0 +1,11 @@ +# Create a AddressScope with all the options + +## Step 00 + +Create a AddressScope 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/addressscope/tests/addressscope-create-minimal/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml new file mode 100644 index 000000000..bfc03fc62 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-create-minimal +status: + resource: + name: addressscope-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: AddressScope + name: addressscope-create-minimal + ref: addressscope +assertAll: + - celExpr: "addressscope.status.id != ''" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..c3909c87f --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-create-minimal/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-create-minimal/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/01-assert.yaml new file mode 100644 index 000000000..99cd6caab --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope' in secret.metadata.finalizers" diff --git a/internal/controllers/addressscope/tests/addressscope-create-minimal/01-delete-secret.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-create-minimal/README.md b/internal/controllers/addressscope/tests/addressscope-create-minimal/README.md new file mode 100644 index 000000000..ab132b070 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a AddressScope with the minimum options + +## Step 00 + +Create a minimal AddressScope, 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/addressscope/tests/addressscope-dependency/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/00-assert.yaml new file mode 100644 index 000000000..f990e7ff8 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/addressscope-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/addressscope-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/addressscope-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/addressscope-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..d73eb617c --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: addressscope-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: addressscope-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-dependency/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/01-assert.yaml new file mode 100644 index 000000000..624cebf5e --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/01-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: AddressScope +metadata: + name: addressscope-dependency-no-project +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..9fb1fa9d5 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic addressscope-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: addressscope-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/addressscope/tests/addressscope-dependency/02-assert.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/02-assert.yaml new file mode 100644 index 000000000..4d3256dd3 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/02-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: addressscope-dependency + ref: project + - apiVersion: v1 + kind: Secret + name: addressscope-dependency + ref: secret +assertAll: + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/addressscope' in project.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/addressscope' in secret.metadata.finalizers" diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/02-delete-dependencies.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..178214e19 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/02-delete-dependencies.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete project.openstack.k-orc.cloud addressscope-dependency --wait=false + namespaced: true + - command: kubectl delete secret addressscope-dependency --wait=false + namespaced: true diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/03-assert.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/03-assert.yaml new file mode 100644 index 000000000..7572987c2 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/03-assert.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get project.openstack.k-orc.cloud addressscope-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret addressscope-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/03-delete-resources.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..e07d5259f --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/03-delete-resources.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-dependency-no-project diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/README.md b/internal/controllers/addressscope/tests/addressscope-dependency/README.md new file mode 100644 index 000000000..1637a8625 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create AddressScopes referencing non-existing resources. Each AddressScope is dependent on other non-existing resource. Verify that the AddressScopes are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the AddressScopes 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 AddressScopes and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-assert.yaml new file mode 100644 index 000000000..dd3974f4f --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Project/addressscope-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Project/addressscope-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..61d1c1ba3 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: addressscope-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: addressscope-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + projectRef: addressscope-import-dependency diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-import-dependency/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/01-assert.yaml new file mode 100644 index 000000000..f961ee99d --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: AddressScope +metadata: + name: addressscope-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Project/addressscope-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Project/addressscope-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..0e7f68c0a --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: addressscope-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 `addressscope-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: addressscope-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/02-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/02-assert.yaml new file mode 100644 index 000000000..5a71d805b --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/02-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-import-dependency + ref: addressscope1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-import-dependency-not-this-one + ref: addressscope2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: addressscope-import-dependency + ref: project +assertAll: + - celExpr: "addressscope1.status.id != addressscope2.status.id" + - celExpr: "addressscope1.status.resource.projectID == project.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..467ab6261 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: addressscope-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: addressscope-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + projectRef: addressscope-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/03-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/03-assert.yaml new file mode 100644 index 000000000..183c12f03 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/03-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get project.openstack.k-orc.cloud addressscope-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..cc2c952aa --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete project.openstack.k-orc.cloud addressscope-import-dependency + namespaced: true diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/04-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/04-assert.yaml new file mode 100644 index 000000000..d8004f4db --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get addressscope.openstack.k-orc.cloud addressscope-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/04-delete-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..9dc761c63 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-import-dependency diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md b/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md new file mode 100644 index 000000000..a4257bfc1 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported AddressScope + +## Step 00 + +Import a AddressScope that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the AddressScope is waiting for the dependency to be ready. + +## Step 01 + +Create a AddressScope matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a AddressScope matching the import filters. + +Verify that the observed status on the imported AddressScope corresponds to the spec of the created AddressScope. + +## 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 AddressScope and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/addressscope/tests/addressscope-import-error/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/00-assert.yaml new file mode 100644 index 000000000..a99503379 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-import-error/00-create-resources.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml new file mode 100644 index 000000000..f8973e7e7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: AddressScope from "import error" test + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: AddressScope from "import error" test + # TODO(scaffolding): add any required field diff --git a/internal/controllers/addressscope/tests/addressscope-import-error/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-import-error/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/01-assert.yaml new file mode 100644 index 000000000..c57c82729 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-import-error/01-import-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml new file mode 100644 index 000000000..3968af498 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: AddressScope from "import error" test diff --git a/internal/controllers/addressscope/tests/addressscope-import-error/README.md b/internal/controllers/addressscope/tests/addressscope-import-error/README.md new file mode 100644 index 000000000..338cf269f --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import-error/README.md @@ -0,0 +1,13 @@ +# Import AddressScope with more than one matching resources + +## Step 00 + +Create two AddressScopes with identical specs. + +## Step 01 + +Ensure that an imported AddressScope with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/addressscope/tests/addressscope-import/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import/00-assert.yaml new file mode 100644 index 000000000..d05edcda5 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-import/00-import-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml new file mode 100644 index 000000000..385c33cb1 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: addressscope-import-external + description: AddressScope addressscope-import-external from "addressscope-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/addressscope/tests/addressscope-import/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-import/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml new file mode 100644 index 000000000..6f4897ab2 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: addressscope-import-external-not-this-one + description: AddressScope addressscope-import-external from "addressscope-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-import/01-create-trap-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..7661c9e14 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml @@ -0,0 +1,17 @@ +--- +# This `addressscope-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: AddressScope +metadata: + name: addressscope-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: AddressScope addressscope-import-external from "addressscope-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml new file mode 100644 index 000000000..9b2f882d9 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-import-external + ref: addressscope1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-import-external-not-this-one + ref: addressscope2 +assertAll: + - celExpr: "addressscope1.status.id != addressscope2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: addressscope-import-external + description: AddressScope addressscope-import-external from "addressscope-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml new file mode 100644 index 000000000..b9ea33592 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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: AddressScope addressscope-import-external from "addressscope-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/addressscope/tests/addressscope-import/README.md b/internal/controllers/addressscope/tests/addressscope-import/README.md new file mode 100644 index 000000000..59f54261e --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-import/README.md @@ -0,0 +1,18 @@ +# Import AddressScope + +## Step 00 + +Import a addressscope that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a addressscope 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 addressscope matching the filter and verify that the observed status on the imported addressscope corresponds to the spec of the created addressscope. +Also, confirm that it does not adopt any addressscope whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml new file mode 100644 index 000000000..b9ba9d3a8 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-update + ref: addressscope +assertAll: + - celExpr: "!has(addressscope.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update +status: + resource: + name: addressscope-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/addressscope/tests/addressscope-update/00-minimal-resource.yaml b/internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml new file mode 100644 index 000000000..994c24cb5 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-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/addressscope/tests/addressscope-update/00-secret.yaml b/internal/controllers/addressscope/tests/addressscope-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-update/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml new file mode 100644 index 000000000..3caafcc31 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update +status: + resource: + name: addressscope-update-updated + description: addressscope-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/addressscope/tests/addressscope-update/01-updated-resource.yaml b/internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml new file mode 100644 index 000000000..07624e3e2 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update +spec: + resource: + name: addressscope-update-updated + description: addressscope-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml b/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml new file mode 100644 index 000000000..c74b5ff06 --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-update + ref: addressscope +assertAll: + - celExpr: "!has(addressscope.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update +status: + resource: + name: addressscope-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/addressscope/tests/addressscope-update/02-reverted-resource.yaml b/internal/controllers/addressscope/tests/addressscope-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-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/addressscope/tests/addressscope-update/README.md b/internal/controllers/addressscope/tests/addressscope-update/README.md new file mode 100644 index 000000000..f2f56c33f --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/README.md @@ -0,0 +1,17 @@ +# Update AddressScope + +## Step 00 + +Create a AddressScope 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/addressscope.go b/internal/osclients/addressscope.go new file mode 100644 index 000000000..464628e0b --- /dev/null +++ b/internal/osclients/addressscope.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/layer3/addressscopes" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type AddressScopeClient interface { + ListAddressScopes(ctx context.Context, listOpts addressscopes.ListOptsBuilder) iter.Seq2[*addressscopes.AddressScope, error] + CreateAddressScope(ctx context.Context, opts addressscopes.CreateOptsBuilder) (*addressscopes.AddressScope, error) + DeleteAddressScope(ctx context.Context, resourceID string) error + GetAddressScope(ctx context.Context, resourceID string) (*addressscopes.AddressScope, error) + UpdateAddressScope(ctx context.Context, id string, opts addressscopes.UpdateOptsBuilder) (*addressscopes.AddressScope, error) +} + +type addressscopeClient struct{ client *gophercloud.ServiceClient } + +// NewAddressScopeClient returns a new OpenStack client. +func NewAddressScopeClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (AddressScopeClient, 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 addressscope service client: %v", err) + } + + return &addressscopeClient{client}, nil +} + +func (c addressscopeClient) ListAddressScopes(ctx context.Context, listOpts addressscopes.ListOptsBuilder) iter.Seq2[*addressscopes.AddressScope, error] { + pager := addressscopes.List(c.client, listOpts) + return func(yield func(*addressscopes.AddressScope, error) bool) { + _ = pager.EachPage(ctx, yieldPage(addressscopes.ExtractAddressScopes, yield)) + } +} + +func (c addressscopeClient) CreateAddressScope(ctx context.Context, opts addressscopes.CreateOptsBuilder) (*addressscopes.AddressScope, error) { + return addressscopes.Create(ctx, c.client, opts).Extract() +} + +func (c addressscopeClient) DeleteAddressScope(ctx context.Context, resourceID string) error { + return addressscopes.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c addressscopeClient) GetAddressScope(ctx context.Context, resourceID string) (*addressscopes.AddressScope, error) { + return addressscopes.Get(ctx, c.client, resourceID).Extract() +} + +func (c addressscopeClient) UpdateAddressScope(ctx context.Context, id string, opts addressscopes.UpdateOptsBuilder) (*addressscopes.AddressScope, error) { + return addressscopes.Update(ctx, c.client, id, opts).Extract() +} + +type addressscopeErrorClient struct{ error } + +// NewAddressScopeErrorClient returns a AddressScopeClient in which every method returns the given error. +func NewAddressScopeErrorClient(e error) AddressScopeClient { + return addressscopeErrorClient{e} +} + +func (e addressscopeErrorClient) ListAddressScopes(_ context.Context, _ addressscopes.ListOptsBuilder) iter.Seq2[*addressscopes.AddressScope, error] { + return func(yield func(*addressscopes.AddressScope, error) bool) { + yield(nil, e.error) + } +} + +func (e addressscopeErrorClient) CreateAddressScope(_ context.Context, _ addressscopes.CreateOptsBuilder) (*addressscopes.AddressScope, error) { + return nil, e.error +} + +func (e addressscopeErrorClient) DeleteAddressScope(_ context.Context, _ string) error { + return e.error +} + +func (e addressscopeErrorClient) GetAddressScope(_ context.Context, _ string) (*addressscopes.AddressScope, error) { + return nil, e.error +} + +func (e addressscopeErrorClient) UpdateAddressScope(_ context.Context, _ string, _ addressscopes.UpdateOptsBuilder) (*addressscopes.AddressScope, error) { + return nil, e.error +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 18bf9bf9e..ebec04a84 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -51,6 +51,12 @@ _Appears in:_ | `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef references the subnet from which to allocate the IP
address. | | MaxLength: 253
MinLength: 1
| + + + + + + #### AllocationPool @@ -1772,6 +1778,8 @@ _Validation:_ _Appears in:_ - [Address](#address) +- [AddressScopeFilter](#addressscopefilter) +- [AddressScopeResourceSpec](#addressscoperesourcespec) - [EndpointFilter](#endpointfilter) - [EndpointResourceSpec](#endpointresourcespec) - [ExternalGateway](#externalgateway) @@ -2179,6 +2187,8 @@ _Validation:_ - Pattern: `^[^,]+$` _Appears in:_ +- [AddressScopeFilter](#addressscopefilter) +- [AddressScopeResourceSpec](#addressscoperesourcespec) - [FlavorFilter](#flavorfilter) - [FlavorResourceSpec](#flavorresourcespec) - [ImageFilter](#imagefilter) From 6133c933c6dde8ca0e6a0dd8a7927244d32188a1 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 25 Feb 2026 20:30:40 +0000 Subject: [PATCH 056/121] scaffolding: change default cloudName on dependency test --- .../tests/import-dependency/02-create-resource.yaml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template b/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template index 3f7c4adb2..763b8e3de 100644 --- a/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template +++ b/cmd/scaffold-controller/data/tests/import-dependency/02-create-resource.yaml.template @@ -22,7 +22,7 @@ metadata: spec: cloudCredentialsRef: # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack-admin + cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: From 2677bfaf37b2f8199964494886c212f9ca09649f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 3 Mar 2026 14:06:32 +0100 Subject: [PATCH 057/121] Bump opentelemetry Bumped with: go get -d go.opentelemetry.io/otel/sdk@v1.41.0 Fixes https://pkg.go.dev/vuln/GO-2026-4394 --- go.mod | 12 ++++++------ go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 3f9d65d29..a83dcb0b7 100644 --- a/go.mod +++ b/go.mod @@ -70,14 +70,14 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/sdk v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -88,7 +88,7 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.39.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.41.0 // indirect diff --git a/go.sum b/go.sum index 337539971..d71d4d506 100644 --- a/go.sum +++ b/go.sum @@ -134,8 +134,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= @@ -152,8 +152,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -168,24 +168,24 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= +go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= +go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -225,8 +225,8 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From cad06598eb1ba2b39aeea9caadff81f4fbb486b8 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Tue, 3 Mar 2026 13:45:42 +0000 Subject: [PATCH 058/121] fix typos in endpoint controller tests' README --- .../endpoint/tests/endpoint-create-full/README.md | 4 ++-- .../endpoint/tests/endpoint-create-minimal/README.md | 2 +- .../endpoint/tests/endpoint-import-dependency/README.md | 6 +++--- .../controllers/endpoint/tests/endpoint-import/README.md | 6 +++--- .../controllers/endpoint/tests/endpoint-update/README.md | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/controllers/endpoint/tests/endpoint-create-full/README.md b/internal/controllers/endpoint/tests/endpoint-create-full/README.md index d625e7b61..2f771930b 100644 --- a/internal/controllers/endpoint/tests/endpoint-create-full/README.md +++ b/internal/controllers/endpoint/tests/endpoint-create-full/README.md @@ -1,8 +1,8 @@ -# Create a Endpoint with all the options +# Create an Endpoint with all the options ## Step 00 -Create a Endpoint using all available fields, and verify that the observed state corresponds to the spec. +Create an Endpoint 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. diff --git a/internal/controllers/endpoint/tests/endpoint-create-minimal/README.md b/internal/controllers/endpoint/tests/endpoint-create-minimal/README.md index b75955791..4deb31e1b 100644 --- a/internal/controllers/endpoint/tests/endpoint-create-minimal/README.md +++ b/internal/controllers/endpoint/tests/endpoint-create-minimal/README.md @@ -1,4 +1,4 @@ -# Create a Endpoint with the minimum options +# Create an Endpoint with the minimum options ## Step 00 diff --git a/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md b/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md index 934ab20fe..8395dc34a 100644 --- a/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md +++ b/internal/controllers/endpoint/tests/endpoint-import-dependency/README.md @@ -2,16 +2,16 @@ ## Step 00 -Import a Endpoint that references other imported resources. The referenced imported resources have no matching resources yet. +Import an Endpoint that references other imported resources. The referenced imported resources have no matching resources yet. Verify the Endpoint is waiting for the dependency to be ready. ## Step 01 -Create a Endpoint matching the import filter, except for referenced resources, and verify that it's not being imported. +Create an Endpoint matching the import filter, except for referenced resources, and verify that it's not being imported. ## Step 02 -Create the referenced resources and a Endpoint matching the import filters. +Create the referenced resources and an Endpoint matching the import filters. Verify that the observed status on the imported Endpoint corresponds to the spec of the created Endpoint. diff --git a/internal/controllers/endpoint/tests/endpoint-import/README.md b/internal/controllers/endpoint/tests/endpoint-import/README.md index d4caf4981..41257fe8c 100644 --- a/internal/controllers/endpoint/tests/endpoint-import/README.md +++ b/internal/controllers/endpoint/tests/endpoint-import/README.md @@ -2,15 +2,15 @@ ## Step 00 -Import a endpoint that matches all fields in the filter, and verify it is waiting for the external resource to be created. +Import an endpoint that matches all fields in the filter, and verify it is waiting for the external resource to be created. ## Step 01 -Create a endpoint 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. +Create an endpoint 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 endpoint matching the filter and verify that the observed status on the imported endpoint corresponds to the spec of the created endpoint. +Create an endpoint matching the filter and verify that the observed status on the imported endpoint corresponds to the spec of the created endpoint. Also, confirm that it does not adopt any endpoint whose name is a superstring of its own. ## Reference diff --git a/internal/controllers/endpoint/tests/endpoint-update/README.md b/internal/controllers/endpoint/tests/endpoint-update/README.md index 393d7fc78..342914f80 100644 --- a/internal/controllers/endpoint/tests/endpoint-update/README.md +++ b/internal/controllers/endpoint/tests/endpoint-update/README.md @@ -2,7 +2,7 @@ ## Step 00 -Create a Endpoint using only mandatory fields. +Create an Endpoint using only mandatory fields. ## Step 01 From 5279623043ff593608eef576bf051d412e372a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 3 Mar 2026 15:14:26 +0100 Subject: [PATCH 059/121] Bump mockgen This should fix the following error we see in the dependabot PRs after switching to go 1.25: Error: ../../../go/pkg/mod/golang.org/x/tools@v0.22.0/internal/tokeninternal/tokeninternal.go:64:9: invalid array length -delta * delta (constant -256 of type int64) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ef8521165..5e037287d 100644 --- a/Makefile +++ b/Makefile @@ -315,7 +315,7 @@ KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= release-0.22 GOLANGCI_LINT_VERSION ?= v2.7.2 -MOCKGEN_VERSION ?= v0.5.0 +MOCKGEN_VERSION ?= v0.6.0 KUTTL_VERSION ?= v0.24.0 GOVULNCHECK_VERSION ?= v1.1.4 OPERATOR_SDK_VERSION ?= v1.41.1 From 9de2d958af66f21cdc5aa5837e75ca2c62169254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 3 Mar 2026 15:23:24 +0100 Subject: [PATCH 060/121] dependabot: drop pins due to older version of go We're now on go 1.25 in the release-1.0 branch. --- .github/dependabot.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4cbbc172e..62321a86b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -75,7 +75,4 @@ updates: # Ignore k8s major and minor bumps and its transitives modules - dependency-name: "k8s.io/*" update-types: ["version-update:semver-major", "version-update:semver-minor"] - # Below dependencies require a newer version of go: - - dependency-name: "github.com/onsi/gomega" - - dependency-name: "golang.org/x/text" ## release-1.0 branch config ends here From 15e83082ab271855634e2ee552130c0b8d1c5515 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:47:57 +0000 Subject: [PATCH 061/121] :seedling:(deps): Bump the all-github-actions group across 1 directory with 3 updates Bumps the all-github-actions group with 3 updates in the / directory: [helm/kind-action](https://github.com/helm/kind-action), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/setup-go](https://github.com/actions/setup-go). Updates `helm/kind-action` from 1.13.0 to 1.14.0 - [Release notes](https://github.com/helm/kind-action/releases) - [Commits](https://github.com/helm/kind-action/compare/92086f6be054225fa813e0a4b13787fc9088faab...ef37e7f390d99f746eb8b610417061a60e82a6cc) Updates `actions/upload-artifact` from 6 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) Updates `actions/setup-go` from 6.2.0 to 6.3.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417) --- updated-dependencies: - dependency-name: helm/kind-action dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions - dependency-name: actions/setup-go dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/e2e.yaml | 4 ++-- .github/workflows/go-lint.yaml | 2 +- .github/workflows/label-pr.yaml | 2 +- .github/workflows/pr-dependabot.yaml | 2 +- .github/workflows/unit.yml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index c3a741a60..069e45339 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -39,7 +39,7 @@ jobs: enabled_services: "openstack-cli-server,neutron-trunk" - name: Deploy a Kind Cluster - uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc with: cluster_name: orc @@ -66,7 +66,7 @@ jobs: - name: Upload logs artifacts on failure if: failure() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: e2e-${{ matrix.name }}-${{ github.run_id }} path: /tmp/artifacts/* diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index 1ab6ece29..bf34bb56d 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -20,7 +20,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 9e796850f..55913c024 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -32,7 +32,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index 6b35cdca2..de3a1a98d 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -24,7 +24,7 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 with: go-version: ${{ steps.vars.outputs.go_version }} - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # tag=v5.0.3 diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 0f6481873..93cb71c31 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -24,7 +24,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 0b21bfaae..f833eb73c 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -25,7 +25,7 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # tag=v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 with: go-version: ${{ steps.vars.outputs.go_version }} - name: Run verify security target From 4ecb5ccf8367bd7eaa986719e9b04b0a9a66c7ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:48:06 +0000 Subject: [PATCH 062/121] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 6 updates Bumps the all-go-mod-patch-and-minor group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [golang.org/x/text](https://github.com/golang/text) | `0.33.0` | `0.34.0` | | [k8s.io/api](https://github.com/kubernetes/api) | `0.34.3` | `0.34.4` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.34.3` | `0.34.4` | | [k8s.io/code-generator](https://github.com/kubernetes/code-generator) | `0.34.3` | `0.34.4` | | [sigs.k8s.io/structured-merge-diff/v6](https://github.com/kubernetes-sigs/structured-merge-diff) | `6.3.1` | `6.3.2` | Updates `golang.org/x/text` from 0.33.0 to 0.34.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.33.0...v0.34.0) Updates `k8s.io/api` from 0.34.3 to 0.34.4 - [Commits](https://github.com/kubernetes/api/compare/v0.34.3...v0.34.4) Updates `k8s.io/apimachinery` from 0.34.3 to 0.34.4 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.3...v0.34.4) Updates `k8s.io/client-go` from 0.34.3 to 0.34.4 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.3...v0.34.4) Updates `k8s.io/code-generator` from 0.34.3 to 0.34.4 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.3...v0.34.4) Updates `sigs.k8s.io/structured-merge-diff/v6` from 6.3.1 to 6.3.2 - [Release notes](https://github.com/kubernetes-sigs/structured-merge-diff/releases) - [Changelog](https://github.com/kubernetes-sigs/structured-merge-diff/blob/master/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/structured-merge-diff/compare/v6.3.1...v6.3.2) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.34.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/api dependency-version: 0.34.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/apimachinery dependency-version: 0.34.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/client-go dependency-version: 0.34.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/code-generator dependency-version: 0.34.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: sigs.k8s.io/structured-merge-diff/v6 dependency-version: 6.3.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index a83dcb0b7..8be1030b5 100644 --- a/go.mod +++ b/go.mod @@ -12,16 +12,16 @@ require ( github.com/onsi/gomega v1.39.1 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 - golang.org/x/text v0.33.0 - k8s.io/api v0.34.3 - k8s.io/apimachinery v0.34.3 - k8s.io/client-go v0.34.3 - k8s.io/code-generator v0.34.3 + golang.org/x/text v0.34.0 + k8s.io/api v0.34.5 + k8s.io/apimachinery v0.34.5 + k8s.io/client-go v0.34.5 + k8s.io/code-generator v0.34.5 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.22.5 - sigs.k8s.io/structured-merge-diff/v6 v6.3.1 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index d71d4d506..6991793ab 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -269,18 +269,18 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/api v0.34.5 h1:+cFkROLIixuQqUZhxizqJKfoT4iwAJneG7NQwqWYyIU= +k8s.io/api v0.34.5/go.mod h1:0RmYc0hpIHEA5s7AyzcPp6j62Z0tRZ+Y7mFFZeXPBuI= k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.5 h1:vXJoeBDaW4D9mayqjP1CrKH8kHyucNRvaLjDJaJOc08= +k8s.io/apimachinery v0.34.5/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= -k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= -k8s.io/code-generator v0.34.3 h1:6ipJKsJZZ9q21BO8I2jEj4OLN3y8/1n4aihKN0xKmQk= -k8s.io/code-generator v0.34.3/go.mod h1:oW73UPYpGLsbRN8Ozkhd6ZzkF8hzFCiYmvEuWZDroI4= +k8s.io/client-go v0.34.5 h1:eZiO7gq+FfrB8hR7/Z5erA+QEbShtp4DMgJdboEzwhY= +k8s.io/client-go v0.34.5/go.mod h1:olcW68aK21BJeIWNXrreRNeZJJfyIbxJ98FYNN/WC5Y= +k8s.io/code-generator v0.34.5 h1:l27oe2+u0RK2PlJJZniGxRR+bog0Gu33murw3XdGGck= +k8s.io/code-generator v0.34.5/go.mod h1:KZMWjn69ikiAVbCK6fywYeZulk+lSLHB68ILEC2pzhc= k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= @@ -299,7 +299,7 @@ sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7np sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= -sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From b5f9d29b8ac7ea20b09c41a81d21a51f6621bab3 Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Tue, 10 Feb 2026 11:14:34 +0000 Subject: [PATCH 063/121] Initial User Controller creation: go run ./cmd/scaffold-controller -interactive=false \ -kind User \ -gophercloud-client NewIdentityV3 \ -gophercloud-module github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users \ -import-dependency Domain \ -optional-create-dependency Domain \ -optional-create-dependency Project Signed-off-by: Daniel Lawton --- api/v1alpha1/user_types.go | 98 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 80 +++++ cmd/models-schema/zz_generated.openapi.go | 119 ++++++++ config/rbac/role.yaml | 2 + config/samples/openstack_v1alpha1_user.yaml | 14 + internal/controllers/user/actuator.go | 287 ++++++++++++++++++ internal/controllers/user/actuator_test.go | 119 ++++++++ internal/controllers/user/controller.go | 135 ++++++++ internal/controllers/user/status.go | 65 ++++ .../tests/user-create-full/00-assert.yaml | 38 +++ .../user-create-full/00-create-resource.yaml | 43 +++ .../tests/user-create-full/00-secret.yaml | 6 + .../user/tests/user-create-full/README.md | 11 + .../tests/user-create-minimal/00-assert.yaml | 27 ++ .../00-create-resource.yaml | 14 + .../tests/user-create-minimal/00-secret.yaml | 6 + .../tests/user-create-minimal/01-assert.yaml | 11 + .../user-create-minimal/01-delete-secret.yaml | 7 + .../user/tests/user-create-minimal/README.md | 15 + .../user/tests/user-dependency/00-assert.yaml | 45 +++ .../00-create-resources-missing-deps.yaml | 40 +++ .../user/tests/user-dependency/00-secret.yaml | 6 + .../user/tests/user-dependency/01-assert.yaml | 45 +++ .../01-create-dependencies.yaml | 32 ++ .../user/tests/user-dependency/02-assert.yaml | 23 ++ .../02-delete-dependencies.yaml | 11 + .../user/tests/user-dependency/03-assert.yaml | 11 + .../user-dependency/03-delete-resources.yaml | 13 + .../user/tests/user-dependency/README.md | 21 ++ .../user-import-dependency/00-assert.yaml | 17 ++ .../00-import-resource.yaml | 26 ++ .../user-import-dependency/00-secret.yaml | 6 + .../user-import-dependency/01-assert.yaml | 32 ++ .../01-create-trap-resource.yaml | 28 ++ .../user-import-dependency/02-assert.yaml | 34 +++ .../02-create-resource.yaml | 27 ++ .../user-import-dependency/03-assert.yaml | 6 + .../03-delete-import-dependencies.yaml | 7 + .../user-import-dependency/04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../tests/user-import-dependency/README.md | 29 ++ .../tests/user-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 28 ++ .../tests/user-import-error/00-secret.yaml | 6 + .../tests/user-import-error/01-assert.yaml | 15 + .../user-import-error/01-import-resource.yaml | 13 + .../user/tests/user-import-error/README.md | 13 + .../user/tests/user-import/00-assert.yaml | 15 + .../tests/user-import/00-import-resource.yaml | 15 + .../user/tests/user-import/00-secret.yaml | 6 + .../user/tests/user-import/01-assert.yaml | 34 +++ .../user-import/01-create-trap-resource.yaml | 17 ++ .../user/tests/user-import/02-assert.yaml | 33 ++ .../tests/user-import/02-create-resource.yaml | 14 + .../user/tests/user-import/README.md | 18 ++ .../user/tests/user-update/00-assert.yaml | 26 ++ .../user-update/00-minimal-resource.yaml | 14 + .../user/tests/user-update/00-secret.yaml | 6 + .../user/tests/user-update/01-assert.yaml | 17 ++ .../user-update/01-updated-resource.yaml | 10 + .../user/tests/user-update/02-assert.yaml | 26 ++ .../user-update/02-reverted-resource.yaml | 7 + .../user/tests/user-update/README.md | 17 ++ internal/osclients/user.go | 104 +++++++ website/docs/crd-reference.md | 10 + 65 files changed, 2063 insertions(+) create mode 100644 api/v1alpha1/user_types.go create mode 100644 config/samples/openstack_v1alpha1_user.yaml create mode 100644 internal/controllers/user/actuator.go create mode 100644 internal/controllers/user/actuator_test.go create mode 100644 internal/controllers/user/controller.go create mode 100644 internal/controllers/user/status.go create mode 100644 internal/controllers/user/tests/user-create-full/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-create-full/00-create-resource.yaml create mode 100644 internal/controllers/user/tests/user-create-full/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-create-full/README.md create mode 100644 internal/controllers/user/tests/user-create-minimal/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/user/tests/user-create-minimal/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-create-minimal/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/user/tests/user-create-minimal/README.md create mode 100644 internal/controllers/user/tests/user-dependency/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/user/tests/user-dependency/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-dependency/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/user/tests/user-dependency/02-assert.yaml create mode 100644 internal/controllers/user/tests/user-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/user/tests/user-dependency/03-assert.yaml create mode 100644 internal/controllers/user/tests/user-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/user/tests/user-dependency/README.md create mode 100644 internal/controllers/user/tests/user-import-dependency/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/02-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/03-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/04-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/user/tests/user-import-dependency/README.md create mode 100644 internal/controllers/user/tests/user-import-error/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-error/00-create-resources.yaml create mode 100644 internal/controllers/user/tests/user-import-error/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-import-error/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-error/01-import-resource.yaml create mode 100644 internal/controllers/user/tests/user-import-error/README.md create mode 100644 internal/controllers/user/tests/user-import/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-import/00-import-resource.yaml create mode 100644 internal/controllers/user/tests/user-import/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-import/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/user/tests/user-import/02-assert.yaml create mode 100644 internal/controllers/user/tests/user-import/02-create-resource.yaml create mode 100644 internal/controllers/user/tests/user-import/README.md create mode 100644 internal/controllers/user/tests/user-update/00-assert.yaml create mode 100644 internal/controllers/user/tests/user-update/00-minimal-resource.yaml create mode 100644 internal/controllers/user/tests/user-update/00-secret.yaml create mode 100644 internal/controllers/user/tests/user-update/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-update/01-updated-resource.yaml create mode 100644 internal/controllers/user/tests/user-update/02-assert.yaml create mode 100644 internal/controllers/user/tests/user-update/02-reverted-resource.yaml create mode 100644 internal/controllers/user/tests/user-update/README.md create mode 100644 internal/osclients/user.go diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go new file mode 100644 index 000000000..50f3a621f --- /dev/null +++ b/api/v1alpha1/user_types.go @@ -0,0 +1,98 @@ +/* +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 + +// UserResourceSpec contains the desired state of the resource. +type UserResourceSpec 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"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the CreateOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users + // + // 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"` +} + +// UserFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type UserFilter 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"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + DomainRef *KubernetesNameRef `json:"domainRef,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/identity/v3/users +} + +// UserResourceStatus represents the observed state of the resource. +type UserResourceStatus 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"` + + // domainID is the ID of the Domain to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + DomainID string `json:"domainID,omitempty"` + + // projectID is the ID of the Project to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the User structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3f9a9f21f..fd9a49ea6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5560,6 +5560,86 @@ func (in *UserDataSpec) DeepCopy() *UserDataSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserFilter) DeepCopyInto(out *UserFilter) { + *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.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserFilter. +func (in *UserFilter) DeepCopy() *UserFilter { + if in == nil { + return nil + } + out := new(UserFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserResourceSpec) DeepCopyInto(out *UserResourceSpec) { + *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.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceSpec. +func (in *UserResourceSpec) DeepCopy() *UserResourceSpec { + if in == nil { + return nil + } + out := new(UserResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserResourceStatus) DeepCopyInto(out *UserResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceStatus. +func (in *UserResourceStatus) DeepCopy() *UserResourceStatus { + if in == nil { + return nil + } + out := new(UserResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Volume) DeepCopyInto(out *Volume) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index d00ee16e4..54c57d419 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -221,6 +221,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportSpec": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserFilter": schema_openstack_resource_controller_v2_api_v1alpha1_UserFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Volume": schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeAttachmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeAttachmentStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeFilter(ref), @@ -10823,6 +10826,122 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref commo } } +func schema_openstack_resource_controller_v2_api_v1alpha1_UserFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserFilter 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: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserResourceSpec 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: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserResourceStatus 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: "", + }, + }, + "domainID": { + SchemaProps: spec.SchemaProps{ + Description: "domainID is the ID of the Domain to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the ID of the Project to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 1bb68f2b9..d3abb550c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -36,6 +36,7 @@ rules: - services - subnets - trunks + - users - volumes - volumetypes verbs: @@ -68,6 +69,7 @@ rules: - services/status - subnets/status - trunks/status + - users/status - volumes/status - volumetypes/status verbs: diff --git a/config/samples/openstack_v1alpha1_user.yaml b/config/samples/openstack_v1alpha1_user.yaml new file mode 100644 index 000000000..3630528ee --- /dev/null +++ b/config/samples/openstack_v1alpha1_user.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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 User + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go new file mode 100644 index 000000000..7fcb50119 --- /dev/null +++ b/internal/controllers/user/actuator.go @@ -0,0 +1,287 @@ +/* +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 user + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" + 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 = users.User + + 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 userActuator struct { + osClient osclients.UserClient + k8sClient client.Client +} + +var _ createResourceActuator = userActuator{} +var _ deleteResourceActuator = userActuator{} + +func (userActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator userActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetUser(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator userActuator) 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 := users.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListUsers(ctx, listOpts), true +} + +func (actuator userActuator) 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 + + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := users.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + DomainID: ptr.Deref(domain.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListUsers(ctx, listOpts), reconcileStatus +} + +func (actuator userActuator) 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 domainID string + if resource.DomainRef != nil { + domain, domainDepRS := domainDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) + if domain != nil { + domainID = ptr.Deref(domain.Status.ID, "") + } + } + + var projectID string + if resource.ProjectRef != nil { + project, projectDepRS := projectDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) + if project != nil { + projectID = ptr.Deref(project.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := users.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + DomainID: domainID, + ProjectID: projectID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateUser(ctx, createOpts) + if err != nil { + // We should require the spec to be updated before retrying a create which returned a conflict + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + return osResource, nil +} + +func (actuator userActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteUser(ctx, resource.ID)) +} + +func (actuator userActuator) 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 := users.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.UpdateUser(ctx, osResource.ID, updateOpts) + + // We should require the spec to be updated before retrying an update which returned a conflict + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts users.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToUserUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["user"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *users.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator userActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type userHelperFactory struct{} + +var _ helperFactory = userHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.User, controller interfaces.ResourceController) (userActuator, 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 userActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return userActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewUserClient() + if err != nil { + return userActuator{}, progress.WrapError(err) + } + + return userActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (userHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return userAdapter{obj} +} + +func (userHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (userHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/user/actuator_test.go b/internal/controllers/user/actuator_test.go new file mode 100644 index 000000000..634aa96a0 --- /dev/null +++ b/internal/controllers/user/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 user + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" + 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 users.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: users.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: users.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.User{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.UserSpec{ + Resource: &orcv1alpha1.UserResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := users.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.UserResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := users.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/user/controller.go b/internal/controllers/user/controller.go new file mode 100644 index 000000000..17188a682 --- /dev/null +++ b/internal/controllers/user/controller.go @@ -0,0 +1,135 @@ +/* +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 user + +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 = "user" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=users,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=users/status,verbs=get;update;patch + +type userReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return userReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (userReconcilerConstructor) GetName() string { + return controllerName +} + +var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.UserList, *orcv1alpha1.Domain]( + "spec.resource.domainRef", + func(user *orcv1alpha1.User) []string { + resource := user.Spec.Resource + if resource == nil || resource.DomainRef == nil { + return nil + } + return []string{string(*resource.DomainRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.UserList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(user *orcv1alpha1.User) []string { + resource := user.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var domainImportDependency = dependency.NewDependency[*orcv1alpha1.UserList, *orcv1alpha1.Domain]( + "spec.import.filter.domainRef", + func(user *orcv1alpha1.User) []string { + resource := user.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.DomainRef == nil { + return nil + } + return []string{string(*resource.Filter.DomainRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + domainWatchEventHandler, err := domainDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainImportWatchEventHandler, err := domainImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + For(&orcv1alpha1.User{}) + + if err := errors.Join( + domainDependency.AddToManager(ctx, mgr), + projectDependency.AddToManager(ctx, mgr), + domainImportDependency.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, userHelperFactory{}, userStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/user/status.go b/internal/controllers/user/status.go new file mode 100644 index 000000000..3606d7d6e --- /dev/null +++ b/internal/controllers/user/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 user + +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 userStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.UserApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.UserStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.User, *osResourceT, *objectApplyT, *statusApplyT] = userStatusWriter{} + +func (userStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.User(name, namespace) +} + +func (userStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.User, 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 (userStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.UserResourceStatus(). + WithDomainID(osResource.DomainID). + WithProjectID(osResource.ProjectID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the UserResourceStatus 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/user/tests/user-create-full/00-assert.yaml b/internal/controllers/user/tests/user-create-full/00-assert.yaml new file mode 100644 index 000000000..92f1909b6 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/00-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-create-full +status: + resource: + name: user-create-full-override + description: User 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: User + name: user-create-full + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-create-full + ref: domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: user-create-full + ref: project +assertAll: + - celExpr: "user.status.id != ''" + - celExpr: "user.status.resource.domainID == domain.status.id" + - celExpr: "user.status.resource.projectID == project.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml new file mode 100644 index 000000000..13256ab09 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-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: Project +metadata: + name: user-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: User +metadata: + name: user-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: user-create-full-override + description: User from "create full" test + domainRef: user-create-full + projectRef: user-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/user/tests/user-create-full/00-secret.yaml b/internal/controllers/user/tests/user-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-create-full/README.md b/internal/controllers/user/tests/user-create-full/README.md new file mode 100644 index 000000000..fd9b95197 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/README.md @@ -0,0 +1,11 @@ +# Create a User with all the options + +## Step 00 + +Create a User 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/user/tests/user-create-minimal/00-assert.yaml b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml new file mode 100644 index 000000000..040d45299 --- /dev/null +++ b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-create-minimal +status: + resource: + name: user-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: User + name: user-create-minimal + ref: user +assertAll: + - celExpr: "user.status.id != ''" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..616e4af5e --- /dev/null +++ b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-create-minimal/00-secret.yaml b/internal/controllers/user/tests/user-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-create-minimal/01-assert.yaml b/internal/controllers/user/tests/user-create-minimal/01-assert.yaml new file mode 100644 index 000000000..549b42729 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user' in secret.metadata.finalizers" diff --git a/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml b/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-create-minimal/README.md b/internal/controllers/user/tests/user-create-minimal/README.md new file mode 100644 index 000000000..4d3dd61ff --- /dev/null +++ b/internal/controllers/user/tests/user-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a User with the minimum options + +## Step 00 + +Create a minimal User, 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/user/tests/user-dependency/00-assert.yaml b/internal/controllers/user/tests/user-dependency/00-assert.yaml new file mode 100644 index 000000000..f67883691 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/00-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/user-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/user-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-domain +status: + conditions: + - type: Available + message: Waiting for Domain/user-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Domain/user-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/user-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/user-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..b8f2b1074 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-domain +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: user-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: user-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/user/tests/user-dependency/00-secret.yaml b/internal/controllers/user/tests/user-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-dependency/01-assert.yaml b/internal/controllers/user/tests/user-dependency/01-assert.yaml new file mode 100644 index 000000000..75041b73c --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/01-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User +metadata: + name: user-dependency-no-domain +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: User +metadata: + name: user-dependency-no-project +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..3857b4862 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic user-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-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: Project +metadata: + name: user-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/user/tests/user-dependency/02-assert.yaml b/internal/controllers/user/tests/user-dependency/02-assert.yaml new file mode 100644 index 000000000..449358e96 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/02-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-dependency + ref: domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: user-dependency + ref: project + - apiVersion: v1 + kind: Secret + name: user-dependency + ref: secret +assertAll: + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in domain.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in project.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in secret.metadata.finalizers" diff --git a/internal/controllers/user/tests/user-dependency/02-delete-dependencies.yaml b/internal/controllers/user/tests/user-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..3f1c6374a --- /dev/null +++ b/internal/controllers/user/tests/user-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 domain.openstack.k-orc.cloud user-dependency --wait=false + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud user-dependency --wait=false + namespaced: true + - command: kubectl delete secret user-dependency --wait=false + namespaced: true diff --git a/internal/controllers/user/tests/user-dependency/03-assert.yaml b/internal/controllers/user/tests/user-dependency/03-assert.yaml new file mode 100644 index 000000000..3eeb3c646 --- /dev/null +++ b/internal/controllers/user/tests/user-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 domain.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret user-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/user/tests/user-dependency/03-delete-resources.yaml b/internal/controllers/user/tests/user-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..478305642 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/03-delete-resources.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-domain +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-project diff --git a/internal/controllers/user/tests/user-dependency/README.md b/internal/controllers/user/tests/user-dependency/README.md new file mode 100644 index 000000000..1eae30592 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create Users referencing non-existing resources. Each User is dependent on other non-existing resource. Verify that the Users are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the Users 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 Users and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/user/tests/user-import-dependency/00-assert.yaml b/internal/controllers/user/tests/user-import-dependency/00-assert.yaml new file mode 100644 index 000000000..bdd2da07d --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..1d74a6008 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: user-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + domainRef: user-import-dependency diff --git a/internal/controllers/user/tests/user-import-dependency/00-secret.yaml b/internal/controllers/user/tests/user-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-import-dependency/01-assert.yaml b/internal/controllers/user/tests/user-import-dependency/01-assert.yaml new file mode 100644 index 000000000..b15197e11 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User +metadata: + name: user-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Domain/user-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..88e199d0d --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-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 `user-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: + domainRef: user-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/user/tests/user-import-dependency/02-assert.yaml b/internal/controllers/user/tests/user-import-dependency/02-assert.yaml new file mode 100644 index 000000000..120234b66 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/02-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-dependency + ref: user1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-dependency-not-this-one + ref: user2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-dependency + ref: domain +assertAll: + - celExpr: "user1.status.id != user2.status.id" + - celExpr: "user1.status.resource.domainID == domain.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import-dependency/02-create-resource.yaml b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..c4fc192ec --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-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: User +metadata: + name: user-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: user-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/user/tests/user-import-dependency/03-assert.yaml b/internal/controllers/user/tests/user-import-dependency/03-assert.yaml new file mode 100644 index 000000000..ed9b4d388 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/03-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get domain.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..b94f3e171 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete domain.openstack.k-orc.cloud user-import-dependency + namespaced: true diff --git a/internal/controllers/user/tests/user-import-dependency/04-assert.yaml b/internal/controllers/user/tests/user-import-dependency/04-assert.yaml new file mode 100644 index 000000000..de589e4a8 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get user.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml b/internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..bf2482dc1 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-dependency diff --git a/internal/controllers/user/tests/user-import-dependency/README.md b/internal/controllers/user/tests/user-import-dependency/README.md new file mode 100644 index 000000000..b43e36ec3 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported User + +## Step 00 + +Import a User that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the User is waiting for the dependency to be ready. + +## Step 01 + +Create a User matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a User matching the import filters. + +Verify that the observed status on the imported User corresponds to the spec of the created User. + +## 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 User and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/user/tests/user-import-error/00-assert.yaml b/internal/controllers/user/tests/user-import-error/00-assert.yaml new file mode 100644 index 000000000..87afc5cff --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User +metadata: + name: user-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/user/tests/user-import-error/00-create-resources.yaml b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml new file mode 100644 index 000000000..ac336e2a3 --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User from "import error" test + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User from "import error" test + # TODO(scaffolding): add any required field diff --git a/internal/controllers/user/tests/user-import-error/00-secret.yaml b/internal/controllers/user/tests/user-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-import-error/01-assert.yaml b/internal/controllers/user/tests/user-import-error/01-assert.yaml new file mode 100644 index 000000000..c8b93f22e --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import-error/01-import-resource.yaml b/internal/controllers/user/tests/user-import-error/01-import-resource.yaml new file mode 100644 index 000000000..f33296ff6 --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: User from "import error" test diff --git a/internal/controllers/user/tests/user-import-error/README.md b/internal/controllers/user/tests/user-import-error/README.md new file mode 100644 index 000000000..b9924c5bd --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/README.md @@ -0,0 +1,13 @@ +# Import User with more than one matching resources + +## Step 00 + +Create two Users with identical specs. + +## Step 01 + +Ensure that an imported User with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/user/tests/user-import/00-assert.yaml b/internal/controllers/user/tests/user-import/00-assert.yaml new file mode 100644 index 000000000..305f139ff --- /dev/null +++ b/internal/controllers/user/tests/user-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import/00-import-resource.yaml b/internal/controllers/user/tests/user-import/00-import-resource.yaml new file mode 100644 index 000000000..0cb7028a9 --- /dev/null +++ b/internal/controllers/user/tests/user-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: user-import-external + description: User user-import-external from "user-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/user/tests/user-import/00-secret.yaml b/internal/controllers/user/tests/user-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-import/01-assert.yaml b/internal/controllers/user/tests/user-import/01-assert.yaml new file mode 100644 index 000000000..97e15325b --- /dev/null +++ b/internal/controllers/user/tests/user-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: user-import-external-not-this-one + description: User user-import-external from "user-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-import/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..e57e94e21 --- /dev/null +++ b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml @@ -0,0 +1,17 @@ +--- +# This `user-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: User +metadata: + name: user-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: User user-import-external from "user-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/user/tests/user-import/02-assert.yaml b/internal/controllers/user/tests/user-import/02-assert.yaml new file mode 100644 index 000000000..f77d13a60 --- /dev/null +++ b/internal/controllers/user/tests/user-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-external + ref: user1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-import-external-not-this-one + ref: user2 +assertAll: + - celExpr: "user1.status.id != user2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: user-import-external + description: User user-import-external from "user-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/user/tests/user-import/02-create-resource.yaml b/internal/controllers/user/tests/user-import/02-create-resource.yaml new file mode 100644 index 000000000..62c644294 --- /dev/null +++ b/internal/controllers/user/tests/user-import/02-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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: User user-import-external from "user-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/user/tests/user-import/README.md b/internal/controllers/user/tests/user-import/README.md new file mode 100644 index 000000000..5901ce2ea --- /dev/null +++ b/internal/controllers/user/tests/user-import/README.md @@ -0,0 +1,18 @@ +# Import User + +## Step 00 + +Import a user that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a user 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 user matching the filter and verify that the observed status on the imported user corresponds to the spec of the created user. +Also, confirm that it does not adopt any user whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/user/tests/user-update/00-assert.yaml b/internal/controllers/user/tests/user-update/00-assert.yaml new file mode 100644 index 000000000..5f1efec41 --- /dev/null +++ b/internal/controllers/user/tests/user-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-update + ref: user +assertAll: + - celExpr: "!has(user.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +status: + resource: + name: user-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/user/tests/user-update/00-minimal-resource.yaml b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml new file mode 100644 index 000000000..1c0526334 --- /dev/null +++ b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-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/user/tests/user-update/00-secret.yaml b/internal/controllers/user/tests/user-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-update/01-assert.yaml b/internal/controllers/user/tests/user-update/01-assert.yaml new file mode 100644 index 000000000..7d07d9a6b --- /dev/null +++ b/internal/controllers/user/tests/user-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +status: + resource: + name: user-update-updated + description: user-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/user/tests/user-update/01-updated-resource.yaml b/internal/controllers/user/tests/user-update/01-updated-resource.yaml new file mode 100644 index 000000000..f34873b14 --- /dev/null +++ b/internal/controllers/user/tests/user-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +spec: + resource: + name: user-update-updated + description: user-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/user/tests/user-update/02-assert.yaml b/internal/controllers/user/tests/user-update/02-assert.yaml new file mode 100644 index 000000000..ed5806e9f --- /dev/null +++ b/internal/controllers/user/tests/user-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-update + ref: user +assertAll: + - celExpr: "!has(user.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update +status: + resource: + name: user-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/user/tests/user-update/02-reverted-resource.yaml b/internal/controllers/user/tests/user-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/user/tests/user-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/user/tests/user-update/README.md b/internal/controllers/user/tests/user-update/README.md new file mode 100644 index 000000000..425c24c0f --- /dev/null +++ b/internal/controllers/user/tests/user-update/README.md @@ -0,0 +1,17 @@ +# Update User + +## Step 00 + +Create a User 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/user.go b/internal/osclients/user.go new file mode 100644 index 000000000..5bf564574 --- /dev/null +++ b/internal/osclients/user.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/identity/v3/users" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type UserClient interface { + ListUsers(ctx context.Context, listOpts users.ListOptsBuilder) iter.Seq2[*users.User, error] + CreateUser(ctx context.Context, opts users.CreateOptsBuilder) (*users.User, error) + DeleteUser(ctx context.Context, resourceID string) error + GetUser(ctx context.Context, resourceID string) (*users.User, error) + UpdateUser(ctx context.Context, id string, opts users.UpdateOptsBuilder) (*users.User, error) +} + +type userClient struct{ client *gophercloud.ServiceClient } + +// NewUserClient returns a new OpenStack client. +func NewUserClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (UserClient, error) { + client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create user service client: %v", err) + } + + return &userClient{client}, nil +} + +func (c userClient) ListUsers(ctx context.Context, listOpts users.ListOptsBuilder) iter.Seq2[*users.User, error] { + pager := users.List(c.client, listOpts) + return func(yield func(*users.User, error) bool) { + _ = pager.EachPage(ctx, yieldPage(users.ExtractUsers, yield)) + } +} + +func (c userClient) CreateUser(ctx context.Context, opts users.CreateOptsBuilder) (*users.User, error) { + return users.Create(ctx, c.client, opts).Extract() +} + +func (c userClient) DeleteUser(ctx context.Context, resourceID string) error { + return users.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c userClient) GetUser(ctx context.Context, resourceID string) (*users.User, error) { + return users.Get(ctx, c.client, resourceID).Extract() +} + +func (c userClient) UpdateUser(ctx context.Context, id string, opts users.UpdateOptsBuilder) (*users.User, error) { + return users.Update(ctx, c.client, id, opts).Extract() +} + +type userErrorClient struct{ error } + +// NewUserErrorClient returns a UserClient in which every method returns the given error. +func NewUserErrorClient(e error) UserClient { + return userErrorClient{e} +} + +func (e userErrorClient) ListUsers(_ context.Context, _ users.ListOptsBuilder) iter.Seq2[*users.User, error] { + return func(yield func(*users.User, error) bool) { + yield(nil, e.error) + } +} + +func (e userErrorClient) CreateUser(_ context.Context, _ users.CreateOptsBuilder) (*users.User, error) { + return nil, e.error +} + +func (e userErrorClient) DeleteUser(_ context.Context, _ string) error { + return e.error +} + +func (e userErrorClient) GetUser(_ context.Context, _ string) (*users.User, error) { + return nil, e.error +} + +func (e userErrorClient) UpdateUser(_ context.Context, _ string, _ users.UpdateOptsBuilder) (*users.User, error) { + return nil, e.error +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 18bf9bf9e..7aa52282e 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1800,6 +1800,8 @@ _Appears in:_ - [TrunkResourceSpec](#trunkresourcespec) - [TrunkSubportSpec](#trunksubportspec) - [UserDataSpec](#userdataspec) +- [UserFilter](#userfilter) +- [UserResourceSpec](#userresourcespec) - [VolumeResourceSpec](#volumeresourcespec) @@ -2203,6 +2205,8 @@ _Appears in:_ - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) - [TrunkResourceSpec](#trunkresourcespec) +- [UserFilter](#userfilter) +- [UserResourceSpec](#userresourcespec) - [VolumeFilter](#volumefilter) - [VolumeResourceSpec](#volumeresourcespec) - [VolumeTypeFilter](#volumetypefilter) @@ -4208,6 +4212,12 @@ _Appears in:_ | `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the user data for this server. | | MaxLength: 253
MinLength: 1
| + + + + + + #### Volume From ae115bc2dd32f03f883d3cb019fe45fd933bf440 Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Tue, 10 Feb 2026 23:39:30 +0000 Subject: [PATCH 064/121] Working User Controller: - E2E tests included - API configured - Controller Note: Password functionality will be implemented in a future PR. Users can still be created and managed, just without password support. Signed-off-by: Daniel Lawton --- PROJECT | 8 + README.md | 1 + api/v1alpha1/user_types.go | 38 +- api/v1alpha1/zz_generated.deepcopy.go | 161 ++++++++- api/v1alpha1/zz_generated.user-resource.go | 179 ++++++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 258 +++++++++++++- cmd/resource-generator/main.go | 3 + .../bases/openstack.k-orc.cloud_users.yaml | 324 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + config/samples/openstack_v1alpha1_user.yaml | 4 +- internal/controllers/user/actuator.go | 44 ++- internal/controllers/user/actuator_test.go | 2 +- internal/controllers/user/controller.go | 6 +- internal/controllers/user/status.go | 11 +- .../tests/user-create-full/00-assert.yaml | 5 +- .../user-create-full/00-create-resource.yaml | 15 +- .../tests/user-create-full/00-secret.yaml | 2 +- .../tests/user-create-full/01-assert.yaml | 10 + .../user-create-full/01-disable-domain.yaml | 7 + .../tests/user-create-minimal/00-assert.yaml | 6 +- .../00-create-resource.yaml | 7 +- .../tests/user-create-minimal/00-secret.yaml | 2 +- .../tests/user-create-minimal/01-assert.yaml | 2 +- .../user-create-minimal/01-delete-secret.yaml | 2 +- .../user/tests/user-dependency/00-assert.yaml | 2 +- .../00-create-resources-missing-deps.yaml | 17 +- .../user/tests/user-dependency/00-secret.yaml | 2 +- .../user/tests/user-dependency/01-assert.yaml | 2 +- .../01-create-dependencies.yaml | 10 +- .../user/tests/user-dependency/02-assert.yaml | 15 +- .../user-dependency/02-disable-domain.yaml | 8 + .../user/tests/user-dependency/03-assert.yaml | 28 +- ...ncies.yaml => 03-delete-dependencies.yaml} | 2 +- .../user/tests/user-dependency/04-assert.yaml | 11 + ...esources.yaml => 04-delete-resources.yaml} | 2 +- .../user/tests/user-dependency/README.md | 8 +- .../user-import-dependency/00-assert.yaml | 2 +- .../00-import-resource.yaml | 6 +- .../user-import-dependency/00-secret.yaml | 2 +- .../user-import-dependency/01-assert.yaml | 2 +- .../01-create-trap-resource.yaml | 10 +- .../user-import-dependency/02-assert.yaml | 2 +- .../02-create-resource.yaml | 8 +- .../user-import-dependency/03-assert.yaml | 15 +- .../03-disable-domain.yaml | 16 + .../user-import-dependency/04-assert.yaml | 4 +- ...aml => 04-delete-import-dependencies.yaml} | 2 +- .../user-import-dependency/05-assert.yaml | 6 + ...-resource.yaml => 05-delete-resource.yaml} | 2 +- .../tests/user-import-dependency/README.md | 8 +- .../tests/user-import-error/00-assert.yaml | 15 + .../00-create-resources.yaml | 22 +- .../tests/user-import-error/00-secret.yaml | 2 +- .../tests/user-import-error/01-assert.yaml | 2 +- .../user-import-error/01-import-resource.yaml | 4 +- .../tests/user-import-error/02-assert.yaml | 10 + .../user-import-error/02-disable-domain.yaml | 8 + .../user/tests/user-import-error/README.md | 6 +- .../user/tests/user-import/00-assert.yaml | 2 +- .../tests/user-import/00-import-resource.yaml | 16 +- .../user/tests/user-import/00-secret.yaml | 2 +- .../user/tests/user-import/01-assert.yaml | 3 +- .../user-import/01-create-trap-resource.yaml | 5 +- .../user/tests/user-import/02-assert.yaml | 2 +- .../tests/user-import/02-create-resource.yaml | 5 +- .../user/tests/user-import/03-assert.yaml | 10 + .../tests/user-import/03-disable-domain.yaml | 8 + .../user/tests/user-import/README.md | 6 +- .../user/tests/user-update/00-assert.yaml | 7 +- .../user-update/00-minimal-resource.yaml | 7 +- .../user/tests/user-update/00-secret.yaml | 2 +- .../user/tests/user-update/01-assert.yaml | 4 +- .../user-update/01-updated-resource.yaml | 2 +- .../user/tests/user-update/02-assert.yaml | 5 +- .../user-update/02-reverted-resource.yaml | 2 +- .../user/tests/user-update/README.md | 2 +- .../controllers/user/zz_generated.adapter.go | 88 +++++ .../user/zz_generated.controller.go | 45 +++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/user.go | 131 +++++++ internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../applyconfiguration/api/v1alpha1/user.go | 281 +++++++++++++++ .../api/v1alpha1/userfilter.go | 52 +++ .../api/v1alpha1/userimport.go | 48 +++ .../api/v1alpha1/userresourcespec.go | 79 +++++ .../api/v1alpha1/userresourcestatus.go | 75 ++++ .../api/v1alpha1/userspec.go | 79 +++++ .../api/v1alpha1/userstatus.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 + .../typed/api/v1alpha1/fake/fake_user.go | 49 +++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../clientset/typed/api/v1alpha1/user.go | 74 ++++ .../api/v1alpha1/interface.go | 7 + .../externalversions/api/v1alpha1/user.go | 102 ++++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + pkg/clients/listers/api/v1alpha1/user.go | 70 ++++ website/docs/crd-reference.md | 133 +++++++ 106 files changed, 2775 insertions(+), 218 deletions(-) create mode 100644 api/v1alpha1/zz_generated.user-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_users.yaml create mode 100644 internal/controllers/user/tests/user-create-full/01-assert.yaml create mode 100644 internal/controllers/user/tests/user-create-full/01-disable-domain.yaml create mode 100644 internal/controllers/user/tests/user-dependency/02-disable-domain.yaml rename internal/controllers/user/tests/user-dependency/{02-delete-dependencies.yaml => 03-delete-dependencies.yaml} (95%) create mode 100644 internal/controllers/user/tests/user-dependency/04-assert.yaml rename internal/controllers/user/tests/user-dependency/{03-delete-resources.yaml => 04-delete-resources.yaml} (89%) create mode 100644 internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml rename internal/controllers/user/tests/user-import-dependency/{03-delete-import-dependencies.yaml => 04-delete-import-dependencies.yaml} (90%) create mode 100644 internal/controllers/user/tests/user-import-dependency/05-assert.yaml rename internal/controllers/user/tests/user-import-dependency/{04-delete-resource.yaml => 05-delete-resource.yaml} (78%) create mode 100644 internal/controllers/user/tests/user-import-error/02-assert.yaml create mode 100644 internal/controllers/user/tests/user-import-error/02-disable-domain.yaml create mode 100644 internal/controllers/user/tests/user-import/03-assert.yaml create mode 100644 internal/controllers/user/tests/user-import/03-disable-domain.yaml create mode 100644 internal/controllers/user/zz_generated.adapter.go create mode 100644 internal/controllers/user/zz_generated.controller.go create mode 100644 internal/osclients/mock/user.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/user.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/userfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/userimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/userspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/user.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/user.go create mode 100644 pkg/clients/listers/api/v1alpha1/user.go diff --git a/PROJECT b/PROJECT index 54fc55950..384bac11f 100644 --- a/PROJECT +++ b/PROJECT @@ -160,6 +160,14 @@ resources: kind: Trunk path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: User + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/README.md b/README.md index c05143838..070ba83b9 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ kubectl delete -f $ORC_RELEASE | service | | ✔ | ✔ | | subnet | | ◐ | ◐ | | trunk | | ✔ | ✔ | +| user | | ◐ | ◐ | | volume | | ◐ | ◐ | | volume type | | ◐ | ◐ | diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index 50f3a621f..faf317660 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -34,18 +34,14 @@ type UserResourceSpec struct { // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - // projectRef is a reference to the ORC Project which this resource is associated with. + // defaultProjectRef is a reference to the Default Project which this resource is associated with. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" - ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the CreateOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users - // - // 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"` + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="defaultProjectRef is immutable" + DefaultProjectRef *KubernetesNameRef `json:"defaultProjectRef,omitempty"` + + // enabled defines whether a user is enabled or disabled + // +optional + Enabled *bool `json:"enabled,omitempty"` } // UserFilter defines an existing resource by its properties @@ -55,19 +51,9 @@ type UserFilter struct { // +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"` - // domainRef is a reference to the ORC Domain which this resource is associated with. // +optional DomainRef *KubernetesNameRef `json:"domainRef,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/identity/v3/users } // UserResourceStatus represents the observed state of the resource. @@ -87,12 +73,12 @@ type UserResourceStatus struct { // +optional DomainID string `json:"domainID,omitempty"` - // projectID is the ID of the Project to which the resource is associated. + // defaultProjectID is the ID of the Default Project to which the user is associated with. // +kubebuilder:validation:MaxLength=1024 // +optional - ProjectID string `json:"projectID,omitempty"` + DefaultProjectID string `json:"defaultProjectID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the User structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users + // enabled defines whether a user is enabled or disabled + // +optional + Enabled bool `json:"enabled,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fd9a49ea6..2b3b6c381 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5540,6 +5540,33 @@ func (in *TrunkSubportStatus) DeepCopy() *TrunkSubportStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *User) DeepCopyInto(out *User) { + *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 User. +func (in *User) DeepCopy() *User { + if in == nil { + return nil + } + out := new(User) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *User) 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 *UserDataSpec) DeepCopyInto(out *UserDataSpec) { *out = *in @@ -5568,11 +5595,6 @@ func (in *UserFilter) DeepCopyInto(out *UserFilter) { *out = new(OpenStackName) **out = **in } - if in.Description != nil { - in, out := &in.Description, &out.Description - *out = new(string) - **out = **in - } if in.DomainRef != nil { in, out := &in.DomainRef, &out.DomainRef *out = new(KubernetesNameRef) @@ -5590,6 +5612,63 @@ func (in *UserFilter) DeepCopy() *UserFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserImport) DeepCopyInto(out *UserImport) { + *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(UserFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserImport. +func (in *UserImport) DeepCopy() *UserImport { + if in == nil { + return nil + } + out := new(UserImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserList) DeepCopyInto(out *UserList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]User, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. +func (in *UserList) DeepCopy() *UserList { + if in == nil { + return nil + } + out := new(UserList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UserList) 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 *UserResourceSpec) DeepCopyInto(out *UserResourceSpec) { *out = *in @@ -5608,11 +5687,16 @@ func (in *UserResourceSpec) DeepCopyInto(out *UserResourceSpec) { *out = new(KubernetesNameRef) **out = **in } - if in.ProjectRef != nil { - in, out := &in.ProjectRef, &out.ProjectRef + if in.DefaultProjectRef != nil { + in, out := &in.DefaultProjectRef, &out.DefaultProjectRef *out = new(KubernetesNameRef) **out = **in } + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceSpec. @@ -5640,6 +5724,69 @@ func (in *UserResourceStatus) DeepCopy() *UserResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserSpec) DeepCopyInto(out *UserSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(UserImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(UserResourceSpec) + (*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 UserSpec. +func (in *UserSpec) DeepCopy() *UserSpec { + if in == nil { + return nil + } + out := new(UserSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserStatus) DeepCopyInto(out *UserStatus) { + *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(UserResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. +func (in *UserStatus) DeepCopy() *UserStatus { + if in == nil { + return nil + } + out := new(UserStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Volume) DeepCopyInto(out *Volume) { *out = *in diff --git a/api/v1alpha1/zz_generated.user-resource.go b/api/v1alpha1/zz_generated.user-resource.go new file mode 100644 index 000000000..b109c1d3f --- /dev/null +++ b/api/v1alpha1/zz_generated.user-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" +) + +// UserImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type UserImport 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 *UserFilter `json:"filter,omitempty"` +} + +// UserSpec 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 UserSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *UserImport `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 *UserResourceSpec `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"` +} + +// UserStatus defines the observed state of an ORC resource. +type UserStatus 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 *UserResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &User{} + +func (i *User) 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" + +// User is the Schema for an ORC resource. +type User 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 UserSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status UserStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// UserList contains a list of User. +type UserList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of User. + // +required + Items []User `json:"items"` +} + +func (l *UserList) GetItems() []User { + return l.Items +} + +func init() { + SchemeBuilder.Register(&User{}, &UserList{}) +} + +func (i *User) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &User{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index bb5b27c69..5f32a837e 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -47,6 +47,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/service" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/subnet" "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" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/volumetype" internalmanager "github.com/k-orc/openstack-resource-controller/v2/internal/manager" @@ -123,6 +124,7 @@ func main() { server.New(scopeFactory), servergroup.New(scopeFactory), project.New(scopeFactory), + user.New(scopeFactory), volume.New(scopeFactory), volumetype.New(scopeFactory), domain.New(scopeFactory), diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 54c57d419..ee55bc2bb 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -220,10 +220,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportSpec": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkSubportStatus": schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.User": schema_openstack_resource_controller_v2_api_v1alpha1_User(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserFilter": schema_openstack_resource_controller_v2_api_v1alpha1_UserFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserImport": schema_openstack_resource_controller_v2_api_v1alpha1_UserImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserList": schema_openstack_resource_controller_v2_api_v1alpha1_UserList(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserSpec": schema_openstack_resource_controller_v2_api_v1alpha1_UserSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserStatus": schema_openstack_resource_controller_v2_api_v1alpha1_UserStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Volume": schema_openstack_resource_controller_v2_api_v1alpha1_Volume(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeAttachmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeAttachmentStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeFilter(ref), @@ -10807,6 +10812,57 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSubportStatus(ref } } +func schema_openstack_resource_controller_v2_api_v1alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "User 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.UserSpec"), + }, + }, + "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.UserStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_UserDataSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -10840,23 +10896,95 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserFilter(ref common. Format: "", }, }, - "description": { + "domainRef": { SchemaProps: spec.SchemaProps{ - Description: "description of the existing resource", + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", Type: []string{"string"}, Format: "", }, }, - "domainRef": { + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserImport 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: "domainRef is a reference to the ORC Domain which this resource is associated with.", + 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.UserFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserList contains a list of User.", + 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 User.", + 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.User"), + }, + }, + }, + }, + }, }, + Required: []string{"items"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } @@ -10888,13 +11016,20 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref c Format: "", }, }, - "projectRef": { + "defaultProjectRef": { SchemaProps: spec.SchemaProps{ - Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Description: "defaultProjectRef is a reference to the Default Project which this resource is associated with.", Type: []string{"string"}, Format: "", }, }, + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "enabled defines whether a user is enabled or disabled", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, @@ -10929,16 +11064,123 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref Format: "", }, }, - "projectID": { + "defaultProjectID": { SchemaProps: spec.SchemaProps{ - Description: "projectID is the ID of the Project to which the resource is associated.", + Description: "defaultProjectID is the ID of the Default Project to which the user is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "enabled defines whether a user is enabled or disabled", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserSpec 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.UserImport"), + }, + }, + "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.UserResourceSpec"), + }, + }, + "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.UserImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_UserStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UserStatus 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.UserResourceStatus"), + }, + }, }, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 0e7444cc1..f395a5153 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -148,6 +148,9 @@ var resources []templateFields = []templateFields{ Name: "Trunk", ExistingOSClient: true, }, + { + Name: "User", + }, { Name: "Volume", }, diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml new file mode 100644 index 000000000..bc8835301 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -0,0 +1,324 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: users.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: User + listKind: UserList + plural: users + singular: user + 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: User 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: + domainRef: + description: domainRef is a reference to the ORC Domain which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + name: + description: name of the existing resource + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + 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: + defaultProjectRef: + description: defaultProjectRef is a reference to the Default Project + which this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: defaultProjectRef is immutable + rule: self == oldSelf + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + domainRef: + description: domainRef is a reference to the ORC Domain which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: domainRef is immutable + rule: self == oldSelf + enabled: + description: enabled defines whether a user is enabled or disabled + type: boolean + 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 + 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: + defaultProjectID: + description: defaultProjectID is the ID of the Default Project + to which the user is associated with. + maxLength: 1024 + type: string + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + domainID: + description: domainID is the ID of the Domain to which the resource + is associated. + maxLength: 1024 + type: string + enabled: + description: enabled defines whether a user is enabled or disabled + type: boolean + name: + description: name is a Human-readable name for the resource. Might + not be unique. + 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 319e67e8a..12db96d3c 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -22,6 +22,7 @@ resources: - bases/openstack.k-orc.cloud_services.yaml - bases/openstack.k-orc.cloud_subnets.yaml - bases/openstack.k-orc.cloud_trunks.yaml +- bases/openstack.k-orc.cloud_users.yaml - bases/openstack.k-orc.cloud_volumes.yaml - bases/openstack.k-orc.cloud_volumetypes.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 488fa1eb7..bee312f87 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -20,6 +20,7 @@ resources: - openstack_v1alpha1_service.yaml - openstack_v1alpha1_subnet.yaml - openstack_v1alpha1_trunk.yaml +- openstack_v1alpha1_user.yaml - openstack_v1alpha1_volume.yaml - openstack_v1alpha1_volumetype.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/openstack_v1alpha1_user.yaml b/config/samples/openstack_v1alpha1_user.yaml index 3630528ee..09067e614 100644 --- a/config/samples/openstack_v1alpha1_user.yaml +++ b/config/samples/openstack_v1alpha1_user.yaml @@ -5,10 +5,8 @@ metadata: name: user-sample 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: description: Sample User - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index 7fcb50119..391205112 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -71,22 +71,14 @@ func (actuator userActuator) ListOSResourcesForAdoption(ctx context.Context, orc 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 := users.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Name: getResourceName(orcObject), } return actuator.osClient.ListUsers(ctx, listOpts), true } func (actuator userActuator) 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 domain, rs := dependency.FetchDependency( @@ -101,10 +93,8 @@ func (actuator userActuator) ListOSResourcesForImport(ctx context.Context, obj o } listOpts := users.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - DomainID: ptr.Deref(domain.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), + DomainID: ptr.Deref(domain.Status.ID, ""), } return actuator.osClient.ListUsers(ctx, listOpts), reconcileStatus @@ -133,8 +123,8 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT } } - var projectID string - if resource.ProjectRef != nil { + var defaultProjectID string + if resource.DefaultProjectRef != nil { project, projectDepRS := projectDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil @@ -142,18 +132,18 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { - projectID = ptr.Deref(project.Status.ID, "") + defaultProjectID = ptr.Deref(project.Status.ID, "") } } if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } createOpts := users.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - DomainID: domainID, - ProjectID: projectID, - // TODO(scaffolding): Add more fields + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + DomainID: domainID, + Enabled: resource.Enabled, + DefaultProjectID: defaultProjectID, } osResource, err := actuator.osClient.CreateUser(ctx, createOpts) @@ -185,8 +175,7 @@ func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT handleNameUpdate(&updateOpts, obj, osResource) handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleEnabledUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -229,7 +218,7 @@ func needsUpdate(updateOpts users.UpdateOpts) (bool, error) { func handleNameUpdate(updateOpts *users.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { name := getResourceName(obj) if osResource.Name != name { - updateOpts.Name = &name + updateOpts.Name = name } } @@ -240,6 +229,13 @@ func handleDescriptionUpdate(updateOpts *users.UpdateOpts, resource *resourceSpe } } +func handleEnabledUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + enabled := ptr.Deref(resource.Enabled, true) + if osResource.Enabled != enabled { + updateOpts.Enabled = &enabled + } +} + func (actuator userActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ actuator.updateResource, diff --git a/internal/controllers/user/actuator_test.go b/internal/controllers/user/actuator_test.go index 634aa96a0..2543da933 100644 --- a/internal/controllers/user/actuator_test.go +++ b/internal/controllers/user/actuator_test.go @@ -37,7 +37,7 @@ func TestNeedsUpdate(t *testing.T) { }, { name: "Updated opts", - updateOpts: users.UpdateOpts{Name: ptr.To("updated")}, + updateOpts: users.UpdateOpts{Name: "updated"}, expectChange: true, }, } diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go index 17188a682..4e432c0c7 100644 --- a/internal/controllers/user/controller.go +++ b/internal/controllers/user/controller.go @@ -64,13 +64,13 @@ var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.UserLi ) var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.UserList, *orcv1alpha1.Project]( - "spec.resource.projectRef", + "spec.resource.defaultProjectRef", func(user *orcv1alpha1.User) []string { resource := user.Spec.Resource - if resource == nil || resource.ProjectRef == nil { + if resource == nil || resource.DefaultProjectRef == nil { return nil } - return []string{string(*resource.ProjectRef)} + return []string{string(*resource.DefaultProjectRef)} }, finalizer, externalObjectFieldOwner, ) diff --git a/internal/controllers/user/status.go b/internal/controllers/user/status.go index 3606d7d6e..0d0f8da51 100644 --- a/internal/controllers/user/status.go +++ b/internal/controllers/user/status.go @@ -51,15 +51,16 @@ func (userStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.User, osR func (userStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.UserResourceStatus(). WithDomainID(osResource.DomainID). - WithProjectID(osResource.ProjectID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the UserResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + WithName(osResource.Name). + WithEnabled(osResource.Enabled) if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } + if osResource.DefaultProjectID != "" { + resourceStatus.WithDefaultProjectID(osResource.DefaultProjectID) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/user/tests/user-create-full/00-assert.yaml b/internal/controllers/user/tests/user-create-full/00-assert.yaml index 92f1909b6..0e9bd2a17 100644 --- a/internal/controllers/user/tests/user-create-full/00-assert.yaml +++ b/internal/controllers/user/tests/user-create-full/00-assert.yaml @@ -7,7 +7,7 @@ status: resource: name: user-create-full-override description: User from "create full" test - # TODO(scaffolding): Add all fields the resource supports + enabled: true conditions: - type: Available status: "True" @@ -34,5 +34,4 @@ resourceRefs: assertAll: - celExpr: "user.status.id != ''" - celExpr: "user.status.resource.domainID == domain.status.id" - - celExpr: "user.status.resource.projectID == project.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "user.status.resource.defaultProjectID == project.status.id" diff --git a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml index 13256ab09..4df449bda 100644 --- a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml +++ b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml @@ -5,11 +5,9 @@ metadata: name: user-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,11 +16,9 @@ metadata: name: user-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 @@ -31,13 +27,12 @@ metadata: name: user-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 resource: name: user-create-full-override description: User from "create full" test domainRef: user-create-full - projectRef: user-create-full - # TODO(scaffolding): Add all fields the resource supports + defaultProjectRef: user-create-full + enabled: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-full/00-secret.yaml b/internal/controllers/user/tests/user-create-full/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-create-full/00-secret.yaml +++ b/internal/controllers/user/tests/user-create-full/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-full/01-assert.yaml b/internal/controllers/user/tests/user-create-full/01-assert.yaml new file mode 100644 index 000000000..5ec861ec8 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/01-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-create-full + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-full/01-disable-domain.yaml b/internal/controllers/user/tests/user-create-full/01-disable-domain.yaml new file mode 100644 index 000000000..c47f2b2a5 --- /dev/null +++ b/internal/controllers/user/tests/user-create-full/01-disable-domain.yaml @@ -0,0 +1,7 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-create-full +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml index 040d45299..950d429bd 100644 --- a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml +++ b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml @@ -6,7 +6,7 @@ metadata: status: resource: name: user-create-minimal - # TODO(scaffolding): Add all fields the resource supports + enabled: true conditions: - type: Available status: "True" @@ -24,4 +24,6 @@ resourceRefs: ref: user assertAll: - celExpr: "user.status.id != ''" - # TODO(scaffolding): Add more checks + - celExpr: "!has(user.status.resource.description)" + - celExpr: "user.status.resource.domainID == 'default'" + - celExpr: "!has(user.status.resource.defaultProjectID)" diff --git a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml index 616e4af5e..c3d2147bf 100644 --- a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml +++ b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml @@ -5,10 +5,7 @@ metadata: name: user-create-minimal 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): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. - resource: {} + resource: {} \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-minimal/00-secret.yaml b/internal/controllers/user/tests/user-create-minimal/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-create-minimal/00-secret.yaml +++ b/internal/controllers/user/tests/user-create-minimal/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-minimal/01-assert.yaml b/internal/controllers/user/tests/user-create-minimal/01-assert.yaml index 549b42729..df50748c8 100644 --- a/internal/controllers/user/tests/user-create-minimal/01-assert.yaml +++ b/internal/controllers/user/tests/user-create-minimal/01-assert.yaml @@ -8,4 +8,4 @@ resourceRefs: ref: secret assertAll: - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/user' in secret.metadata.finalizers" + - celExpr: "'openstack.k-orc.cloud/user' in secret.metadata.finalizers" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml b/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml index 1620791b9..c3e557604 100644 --- a/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml +++ b/internal/controllers/user/tests/user-create-minimal/01-delete-secret.yaml @@ -4,4 +4,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/00-assert.yaml b/internal/controllers/user/tests/user-dependency/00-assert.yaml index f67883691..388f70d82 100644 --- a/internal/controllers/user/tests/user-dependency/00-assert.yaml +++ b/internal/controllers/user/tests/user-dependency/00-assert.yaml @@ -42,4 +42,4 @@ status: - type: Progressing message: Waiting for Project/user-dependency to be created status: "True" - reason: Progressing + reason: Progressing \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml index b8f2b1074..c5b59dafa 100644 --- a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml @@ -5,26 +5,23 @@ metadata: name: user-dependency-no-domain 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: domainRef: user-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: name: user-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: user-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + defaultProjectRef: user-dependency --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -32,9 +29,7 @@ metadata: name: user-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: user-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: {} \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/00-secret.yaml b/internal/controllers/user/tests/user-dependency/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-dependency/00-secret.yaml +++ b/internal/controllers/user/tests/user-dependency/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/01-assert.yaml b/internal/controllers/user/tests/user-dependency/01-assert.yaml index 75041b73c..30bfee417 100644 --- a/internal/controllers/user/tests/user-dependency/01-assert.yaml +++ b/internal/controllers/user/tests/user-dependency/01-assert.yaml @@ -42,4 +42,4 @@ status: - type: Progressing message: OpenStack resource is up to date status: "False" - reason: Success + reason: Success \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml index 3857b4862..4a292db93 100644 --- a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml +++ b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml @@ -11,11 +11,9 @@ metadata: name: user-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,7 @@ metadata: name: user-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: {} + resource: {} \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/02-assert.yaml b/internal/controllers/user/tests/user-dependency/02-assert.yaml index 449358e96..a5a8dd7f7 100644 --- a/internal/controllers/user/tests/user-dependency/02-assert.yaml +++ b/internal/controllers/user/tests/user-dependency/02-assert.yaml @@ -6,18 +6,5 @@ resourceRefs: kind: Domain name: user-dependency ref: domain - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: user-dependency - ref: project - - apiVersion: v1 - kind: Secret - name: user-dependency - ref: secret assertAll: - - celExpr: "domain.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/user' in domain.metadata.finalizers" - - celExpr: "project.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/user' in project.metadata.finalizers" - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/user' in secret.metadata.finalizers" + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/02-disable-domain.yaml b/internal/controllers/user/tests/user-dependency/02-disable-domain.yaml new file mode 100644 index 000000000..494189845 --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/02-disable-domain.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-dependency +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/03-assert.yaml b/internal/controllers/user/tests/user-dependency/03-assert.yaml index 3eeb3c646..7f6b3c162 100644 --- a/internal/controllers/user/tests/user-dependency/03-assert.yaml +++ b/internal/controllers/user/tests/user-dependency/03-assert.yaml @@ -1,11 +1,23 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -commands: -# Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get domain.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get secret user-dependency --namespace $NAMESPACE" - skipLogOutput: true +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-dependency + ref: domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: user-dependency + ref: project + - apiVersion: v1 + kind: Secret + name: user-dependency + ref: secret +assertAll: + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in domain.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in project.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/user' in secret.metadata.finalizers" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/02-delete-dependencies.yaml b/internal/controllers/user/tests/user-dependency/03-delete-dependencies.yaml similarity index 95% rename from internal/controllers/user/tests/user-dependency/02-delete-dependencies.yaml rename to internal/controllers/user/tests/user-dependency/03-delete-dependencies.yaml index 3f1c6374a..6e751521b 100644 --- a/internal/controllers/user/tests/user-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/user/tests/user-dependency/03-delete-dependencies.yaml @@ -8,4 +8,4 @@ commands: - command: kubectl delete project.openstack.k-orc.cloud user-dependency --wait=false namespaced: true - command: kubectl delete secret user-dependency --wait=false - namespaced: true + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/04-assert.yaml b/internal/controllers/user/tests/user-dependency/04-assert.yaml new file mode 100644 index 000000000..a33f164ef --- /dev/null +++ b/internal/controllers/user/tests/user-dependency/04-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 domain.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud user-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret user-dependency --namespace $NAMESPACE" + skipLogOutput: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/03-delete-resources.yaml b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml similarity index 89% rename from internal/controllers/user/tests/user-dependency/03-delete-resources.yaml rename to internal/controllers/user/tests/user-dependency/04-delete-resources.yaml index 478305642..8054e0ebc 100644 --- a/internal/controllers/user/tests/user-dependency/03-delete-resources.yaml +++ b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml @@ -10,4 +10,4 @@ delete: name: user-dependency-no-domain - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User - name: user-dependency-no-project + name: user-dependency-no-project \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/README.md b/internal/controllers/user/tests/user-dependency/README.md index 1eae30592..cda61e315 100644 --- a/internal/controllers/user/tests/user-dependency/README.md +++ b/internal/controllers/user/tests/user-dependency/README.md @@ -10,12 +10,16 @@ Create the missing dependencies and verify all the Users are available. ## Step 02 -Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. +Disable the domain dependency to allow KUTTL to cleanup resources without any issues. ## Step 03 +Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. + +## Step 04 + Delete the Users and validate that all resources are gone. ## Reference -https://k-orc.cloud/development/writing-tests/#dependency +https://k-orc.cloud/development/writing-tests/#dependency \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/00-assert.yaml b/internal/controllers/user/tests/user-import-dependency/00-assert.yaml index bdd2da07d..607b6d635 100644 --- a/internal/controllers/user/tests/user-import-dependency/00-assert.yaml +++ b/internal/controllers/user/tests/user-import-dependency/00-assert.yaml @@ -14,4 +14,4 @@ status: message: |- Waiting for Domain/user-import-dependency to be ready status: "True" - reason: Progressing + reason: Progressing \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml index 1d74a6008..0681c805b 100644 --- a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml @@ -5,7 +5,7 @@ metadata: name: user-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -18,9 +18,9 @@ metadata: name: user-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - domainRef: user-import-dependency + domainRef: user-import-dependency \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/00-secret.yaml b/internal/controllers/user/tests/user-import-dependency/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-import-dependency/00-secret.yaml +++ b/internal/controllers/user/tests/user-import-dependency/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/01-assert.yaml b/internal/controllers/user/tests/user-import-dependency/01-assert.yaml index b15197e11..eaa58861a 100644 --- a/internal/controllers/user/tests/user-import-dependency/01-assert.yaml +++ b/internal/controllers/user/tests/user-import-dependency/01-assert.yaml @@ -29,4 +29,4 @@ status: message: |- Waiting for Domain/user-import-dependency to be ready status: "True" - reason: Progressing + reason: Progressing \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml index 88e199d0d..7154af7ba 100644 --- a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml @@ -5,11 +5,9 @@ metadata: name: user-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: {} --- # This `user-import-dependency-not-this-one` should not be picked by the import filter @@ -19,10 +17,8 @@ metadata: name: user-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: - domainRef: user-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + domainRef: user-import-dependency-not-this-one \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/02-assert.yaml b/internal/controllers/user/tests/user-import-dependency/02-assert.yaml index 120234b66..62decb321 100644 --- a/internal/controllers/user/tests/user-import-dependency/02-assert.yaml +++ b/internal/controllers/user/tests/user-import-dependency/02-assert.yaml @@ -31,4 +31,4 @@ status: - type: Progressing message: OpenStack resource is up to date status: "False" - reason: Success + reason: Success \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml index c4fc192ec..ea64cab75 100644 --- a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml @@ -5,11 +5,9 @@ metadata: name: user-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,10 +16,8 @@ metadata: name: user-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - domainRef: user-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + domainRef: user-import-dependency-external \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/03-assert.yaml b/internal/controllers/user/tests/user-import-dependency/03-assert.yaml index ed9b4d388..32c8d86cd 100644 --- a/internal/controllers/user/tests/user-import-dependency/03-assert.yaml +++ b/internal/controllers/user/tests/user-import-dependency/03-assert.yaml @@ -1,6 +1,15 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -commands: -- script: "! kubectl get domain.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" - skipLogOutput: true +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-dependency-external + ref: domain1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-dependency-not-this-one + ref: domain2 +assertAll: + - celExpr: "domain1.status.resource.enabled == false" + - celExpr: "domain2.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml b/internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml new file mode 100644 index 000000000..9265b4158 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/03-disable-domain.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency-external +spec: + resource: + enabled: false +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-dependency-not-this-one +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/04-assert.yaml b/internal/controllers/user/tests/user-import-dependency/04-assert.yaml index de589e4a8..7c39b1cdb 100644 --- a/internal/controllers/user/tests/user-import-dependency/04-assert.yaml +++ b/internal/controllers/user/tests/user-import-dependency/04-assert.yaml @@ -2,5 +2,5 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -- script: "! kubectl get user.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" - skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" + skipLogOutput: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/user/tests/user-import-dependency/04-delete-import-dependencies.yaml similarity index 90% rename from internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml rename to internal/controllers/user/tests/user-import-dependency/04-delete-import-dependencies.yaml index b94f3e171..32de53c66 100644 --- a/internal/controllers/user/tests/user-import-dependency/03-delete-import-dependencies.yaml +++ b/internal/controllers/user/tests/user-import-dependency/04-delete-import-dependencies.yaml @@ -4,4 +4,4 @@ kind: TestStep commands: # We should be able to delete the import dependencies - command: kubectl delete domain.openstack.k-orc.cloud user-import-dependency - namespaced: true + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/05-assert.yaml b/internal/controllers/user/tests/user-import-dependency/05-assert.yaml new file mode 100644 index 000000000..1f8812738 --- /dev/null +++ b/internal/controllers/user/tests/user-import-dependency/05-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get user.openstack.k-orc.cloud user-import-dependency --namespace $NAMESPACE" + skipLogOutput: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml b/internal/controllers/user/tests/user-import-dependency/05-delete-resource.yaml similarity index 78% rename from internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml rename to internal/controllers/user/tests/user-import-dependency/05-delete-resource.yaml index bf2482dc1..bebcdd8da 100644 --- a/internal/controllers/user/tests/user-import-dependency/04-delete-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/05-delete-resource.yaml @@ -4,4 +4,4 @@ kind: TestStep delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User - name: user-import-dependency + name: user-import-dependency \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/README.md b/internal/controllers/user/tests/user-import-dependency/README.md index b43e36ec3..3f8526f24 100644 --- a/internal/controllers/user/tests/user-import-dependency/README.md +++ b/internal/controllers/user/tests/user-import-dependency/README.md @@ -17,13 +17,17 @@ Verify that the observed status on the imported User corresponds to the spec of ## Step 03 +Disable the domain dependencies so KUTTL can clean the resources without failing. + +## Step 04 + 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 +## Step 05 Delete the User and validate that all resources are gone. ## Reference -https://k-orc.cloud/development/writing-tests/#import-dependency +https://k-orc.cloud/development/writing-tests/#import-dependency \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/00-assert.yaml b/internal/controllers/user/tests/user-import-error/00-assert.yaml index 87afc5cff..4efbfbde0 100644 --- a/internal/controllers/user/tests/user-import-error/00-assert.yaml +++ b/internal/controllers/user/tests/user-import-error/00-assert.yaml @@ -1,5 +1,20 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-error-domain +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: User metadata: name: user-import-error-external-1 diff --git a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml index ac336e2a3..6f4e6c034 100644 --- a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml +++ b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml @@ -1,17 +1,28 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-error-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: name: user-import-error-external-1 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: description: User from "import error" test - # TODO(scaffolding): add any required field + domainRef: user-import-error-domain + --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -19,10 +30,9 @@ metadata: name: user-import-error-external-2 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: description: User from "import error" test - # TODO(scaffolding): add any required field + domainRef: user-import-error-domain \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/00-secret.yaml b/internal/controllers/user/tests/user-import-error/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-import-error/00-secret.yaml +++ b/internal/controllers/user/tests/user-import-error/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/01-assert.yaml b/internal/controllers/user/tests/user-import-error/01-assert.yaml index c8b93f22e..ddb4c0caf 100644 --- a/internal/controllers/user/tests/user-import-error/01-assert.yaml +++ b/internal/controllers/user/tests/user-import-error/01-assert.yaml @@ -12,4 +12,4 @@ status: - type: Progressing message: found more than one matching OpenStack resource during import status: "False" - reason: InvalidConfiguration + reason: InvalidConfiguration \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/01-import-resource.yaml b/internal/controllers/user/tests/user-import-error/01-import-resource.yaml index f33296ff6..6984d8c0b 100644 --- a/internal/controllers/user/tests/user-import-error/01-import-resource.yaml +++ b/internal/controllers/user/tests/user-import-error/01-import-resource.yaml @@ -5,9 +5,9 @@ metadata: name: user-import-error spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: - description: User from "import error" test + domainRef: user-import-error-domain \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/02-assert.yaml b/internal/controllers/user/tests/user-import-error/02-assert.yaml new file mode 100644 index 000000000..11d23cbeb --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/02-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-error-domain + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/02-disable-domain.yaml b/internal/controllers/user/tests/user-import-error/02-disable-domain.yaml new file mode 100644 index 000000000..e05ab92ce --- /dev/null +++ b/internal/controllers/user/tests/user-import-error/02-disable-domain.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-error-domain +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/README.md b/internal/controllers/user/tests/user-import-error/README.md index b9924c5bd..3790cfe6e 100644 --- a/internal/controllers/user/tests/user-import-error/README.md +++ b/internal/controllers/user/tests/user-import-error/README.md @@ -8,6 +8,10 @@ Create two Users with identical specs. Ensure that an imported User with a filter matching the resources returns an error. +## Step 02 + +Disable the domain dependency so KUTTL can clean the resources without failing. + ## Reference -https://k-orc.cloud/development/writing-tests/#import-error +https://k-orc.cloud/development/writing-tests/#import-error \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/00-assert.yaml b/internal/controllers/user/tests/user-import/00-assert.yaml index 305f139ff..44f6015ad 100644 --- a/internal/controllers/user/tests/user-import/00-assert.yaml +++ b/internal/controllers/user/tests/user-import/00-assert.yaml @@ -12,4 +12,4 @@ status: - type: Progressing message: Waiting for OpenStack resource to be created externally status: "True" - reason: Progressing + reason: Progressing \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/00-import-resource.yaml b/internal/controllers/user/tests/user-import/00-import-resource.yaml index 0cb7028a9..d8b7199fa 100644 --- a/internal/controllers/user/tests/user-import/00-import-resource.yaml +++ b/internal/controllers/user/tests/user-import/00-import-resource.yaml @@ -1,15 +1,25 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: name: user-import spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: name: user-import-external - description: User user-import-external from "user-import" test - # TODO(scaffolding): Add all fields supported by the filter + domainRef: user-import-external \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/00-secret.yaml b/internal/controllers/user/tests/user-import/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-import/00-secret.yaml +++ b/internal/controllers/user/tests/user-import/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/01-assert.yaml b/internal/controllers/user/tests/user-import/01-assert.yaml index 97e15325b..bf4a0d9bc 100644 --- a/internal/controllers/user/tests/user-import/01-assert.yaml +++ b/internal/controllers/user/tests/user-import/01-assert.yaml @@ -16,7 +16,6 @@ status: resource: name: user-import-external-not-this-one description: User user-import-external from "user-import" test - # TODO(scaffolding): Add fields necessary to match filter --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -31,4 +30,4 @@ status: - type: Progressing message: Waiting for OpenStack resource to be created externally status: "True" - reason: Progressing + reason: Progressing \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml index e57e94e21..ea393341f 100644 --- a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml +++ b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml @@ -8,10 +8,9 @@ metadata: name: user-import-external-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: description: User user-import-external from "user-import" test - # TODO(scaffolding): Add fields necessary to match filter + domainRef: user-import-external \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/02-assert.yaml b/internal/controllers/user/tests/user-import/02-assert.yaml index f77d13a60..ec194ff34 100644 --- a/internal/controllers/user/tests/user-import/02-assert.yaml +++ b/internal/controllers/user/tests/user-import/02-assert.yaml @@ -30,4 +30,4 @@ status: resource: name: user-import-external description: User user-import-external from "user-import" test - # TODO(scaffolding): Add all fields the resource supports + enabled: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/02-create-resource.yaml b/internal/controllers/user/tests/user-import/02-create-resource.yaml index 62c644294..43a43ef04 100644 --- a/internal/controllers/user/tests/user-import/02-create-resource.yaml +++ b/internal/controllers/user/tests/user-import/02-create-resource.yaml @@ -5,10 +5,9 @@ metadata: name: user-import-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 resource: description: User user-import-external from "user-import" test - # TODO(scaffolding): Add fields necessary to match filter + domainRef: user-import-external \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/03-assert.yaml b/internal/controllers/user/tests/user-import/03-assert.yaml new file mode 100644 index 000000000..2f7e63d0f --- /dev/null +++ b/internal/controllers/user/tests/user-import/03-assert.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: user-import-external + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/03-disable-domain.yaml b/internal/controllers/user/tests/user-import/03-disable-domain.yaml new file mode 100644 index 000000000..aff09aed9 --- /dev/null +++ b/internal/controllers/user/tests/user-import/03-disable-domain.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-import-external +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/README.md b/internal/controllers/user/tests/user-import/README.md index 5901ce2ea..53be264fa 100644 --- a/internal/controllers/user/tests/user-import/README.md +++ b/internal/controllers/user/tests/user-import/README.md @@ -13,6 +13,10 @@ Create a user whose name is a superstring of the one specified in the import fil Create a user matching the filter and verify that the observed status on the imported user corresponds to the spec of the created user. Also, confirm that it does not adopt any user whose name is a superstring of its own. +## Step 03 + +Disable the domain dependency so KUTTL can clean the resources without failing. + ## Reference -https://k-orc.cloud/development/writing-tests/#import +https://k-orc.cloud/development/writing-tests/#import \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/00-assert.yaml b/internal/controllers/user/tests/user-update/00-assert.yaml index 5f1efec41..1cd41ff5a 100644 --- a/internal/controllers/user/tests/user-update/00-assert.yaml +++ b/internal/controllers/user/tests/user-update/00-assert.yaml @@ -8,6 +8,9 @@ resourceRefs: ref: user assertAll: - celExpr: "!has(user.status.resource.description)" + - celExpr: "user.status.resource.domainID == 'default'" + - celExpr: "!has(user.status.resource.defaultProjectID)" + - celExpr: "!has(user.status.resource.passwordExpiresAt)" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -16,11 +19,11 @@ metadata: status: resource: name: user-update - # TODO(scaffolding): Add matches for more fields + enabled: true conditions: - type: Available status: "True" reason: Success - type: Progressing status: "False" - reason: Success + reason: Success \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml index 1c0526334..02960585c 100644 --- a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml +++ b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml @@ -5,10 +5,7 @@ metadata: name: user-update spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated - cloudName: openstack + cloudName: openstack-admin 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: {} \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/00-secret.yaml b/internal/controllers/user/tests/user-update/00-secret.yaml index 045711ee7..082860af5 100644 --- a/internal/controllers/user/tests/user-update/00-secret.yaml +++ b/internal/controllers/user/tests/user-update/00-secret.yaml @@ -3,4 +3,4 @@ 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 + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/01-assert.yaml b/internal/controllers/user/tests/user-update/01-assert.yaml index 7d07d9a6b..0a9fa6937 100644 --- a/internal/controllers/user/tests/user-update/01-assert.yaml +++ b/internal/controllers/user/tests/user-update/01-assert.yaml @@ -7,11 +7,11 @@ status: resource: name: user-update-updated description: user-update-updated - # TODO(scaffolding): match all fields that were modified + enabled: false conditions: - type: Available status: "True" reason: Success - type: Progressing status: "False" - reason: Success + reason: Success \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/01-updated-resource.yaml b/internal/controllers/user/tests/user-update/01-updated-resource.yaml index f34873b14..4cbafe8c3 100644 --- a/internal/controllers/user/tests/user-update/01-updated-resource.yaml +++ b/internal/controllers/user/tests/user-update/01-updated-resource.yaml @@ -7,4 +7,4 @@ spec: resource: name: user-update-updated description: user-update-updated - # TODO(scaffolding): update all mutable fields + enabled: false \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/02-assert.yaml b/internal/controllers/user/tests/user-update/02-assert.yaml index ed5806e9f..1c70b64e1 100644 --- a/internal/controllers/user/tests/user-update/02-assert.yaml +++ b/internal/controllers/user/tests/user-update/02-assert.yaml @@ -8,6 +8,7 @@ resourceRefs: ref: user assertAll: - celExpr: "!has(user.status.resource.description)" + - celExpr: "!has(user.status.resource.passwordExpiresAt)" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -16,11 +17,11 @@ metadata: status: resource: name: user-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + enabled: true conditions: - type: Available status: "True" reason: Success - type: Progressing status: "False" - reason: Success + reason: Success \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/02-reverted-resource.yaml b/internal/controllers/user/tests/user-update/02-reverted-resource.yaml index 2c6c253ff..ec043aae6 100644 --- a/internal/controllers/user/tests/user-update/02-reverted-resource.yaml +++ b/internal/controllers/user/tests/user-update/02-reverted-resource.yaml @@ -4,4 +4,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - command: kubectl replace -f 00-minimal-resource.yaml - namespaced: true + namespaced: true \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/README.md b/internal/controllers/user/tests/user-update/README.md index 425c24c0f..13da45548 100644 --- a/internal/controllers/user/tests/user-update/README.md +++ b/internal/controllers/user/tests/user-update/README.md @@ -14,4 +14,4 @@ Revert the resource to its original value and verify that the resulting object m ## Reference -https://k-orc.cloud/development/writing-tests/#update +https://k-orc.cloud/development/writing-tests/#update \ No newline at end of file diff --git a/internal/controllers/user/zz_generated.adapter.go b/internal/controllers/user/zz_generated.adapter.go new file mode 100644 index 000000000..718a1ef46 --- /dev/null +++ b/internal/controllers/user/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 user + +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.User + orcObjectListT = orcv1alpha1.UserList + resourceSpecT = orcv1alpha1.UserResourceSpec + filterT = orcv1alpha1.UserFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = userAdapter +) + +type userAdapter struct { + *orcv1alpha1.User +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.User +} + +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/user/zz_generated.controller.go b/internal/controllers/user/zz_generated.controller.go new file mode 100644 index 000000000..667cf95e8 --- /dev/null +++ b/internal/controllers/user/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 user + +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 176f3bc93..8e2b83583 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -53,6 +53,9 @@ import ( //go:generate mockgen -package mock -destination=service.go -source=../service.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ServiceClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt service.go > _service.go && mv _service.go service.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" + //go:generate mockgen -package mock -destination=volume.go -source=../volume.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock VolumeClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt volume.go > _volume.go && mv _volume.go volume.go" diff --git a/internal/osclients/mock/user.go b/internal/osclients/mock/user.go new file mode 100644 index 000000000..7ce0e5cd1 --- /dev/null +++ b/internal/osclients/mock/user.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: ../user.go +// +// Generated by this command: +// +// mockgen -package mock -destination=user.go -source=../user.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock UserClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + users "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" + gomock "go.uber.org/mock/gomock" +) + +// MockUserClient is a mock of UserClient interface. +type MockUserClient struct { + ctrl *gomock.Controller + recorder *MockUserClientMockRecorder + isgomock struct{} +} + +// MockUserClientMockRecorder is the mock recorder for MockUserClient. +type MockUserClientMockRecorder struct { + mock *MockUserClient +} + +// NewMockUserClient creates a new mock instance. +func NewMockUserClient(ctrl *gomock.Controller) *MockUserClient { + mock := &MockUserClient{ctrl: ctrl} + mock.recorder = &MockUserClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUserClient) EXPECT() *MockUserClientMockRecorder { + return m.recorder +} + +// CreateUser mocks base method. +func (m *MockUserClient) CreateUser(ctx context.Context, opts users.CreateOptsBuilder) (*users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateUser", ctx, opts) + ret0, _ := ret[0].(*users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateUser indicates an expected call of CreateUser. +func (mr *MockUserClientMockRecorder) CreateUser(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUser", reflect.TypeOf((*MockUserClient)(nil).CreateUser), ctx, opts) +} + +// DeleteUser mocks base method. +func (m *MockUserClient) DeleteUser(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteUser", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteUser indicates an expected call of DeleteUser. +func (mr *MockUserClientMockRecorder) DeleteUser(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUser", reflect.TypeOf((*MockUserClient)(nil).DeleteUser), ctx, resourceID) +} + +// GetUser mocks base method. +func (m *MockUserClient) GetUser(ctx context.Context, resourceID string) (*users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", ctx, resourceID) + ret0, _ := ret[0].(*users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser. +func (mr *MockUserClientMockRecorder) GetUser(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserClient)(nil).GetUser), ctx, resourceID) +} + +// ListUsers mocks base method. +func (m *MockUserClient) ListUsers(ctx context.Context, listOpts users.ListOptsBuilder) iter.Seq2[*users.User, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListUsers", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*users.User, error]) + return ret0 +} + +// ListUsers indicates an expected call of ListUsers. +func (mr *MockUserClientMockRecorder) ListUsers(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsers", reflect.TypeOf((*MockUserClient)(nil).ListUsers), ctx, listOpts) +} + +// UpdateUser mocks base method. +func (m *MockUserClient) UpdateUser(ctx context.Context, id string, opts users.UpdateOptsBuilder) (*users.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateUser", ctx, id, opts) + ret0, _ := ret[0].(*users.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateUser indicates an expected call of UpdateUser. +func (mr *MockUserClientMockRecorder) UpdateUser(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockUserClient)(nil).UpdateUser), ctx, id, opts) +} diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 9cc49cd03..ed82b4b85 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -44,6 +44,7 @@ type MockScopeFactory struct { NetworkClient *mock.MockNetworkClient RoleClient *mock.MockRoleClient ServiceClient *mock.MockServiceClient + UserClient *mock.MockUserClient VolumeClient *mock.MockVolumeClient VolumeTypeClient *mock.MockVolumeTypeClient @@ -61,6 +62,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { networkClient := mock.NewMockNetworkClient(mockCtrl) roleClient := mock.NewMockRoleClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) + userClient := mock.NewMockUserClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) @@ -75,6 +77,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { NetworkClient: networkClient, RoleClient: roleClient, ServiceClient: serviceClient, + UserClient: userClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, } @@ -107,6 +110,10 @@ func (f *MockScopeFactory) NewIdentityClient() (osclients.IdentityClient, error) return f.IdentityClient, nil } +func (f *MockScopeFactory) NewUserClient() (osclients.UserClient, error) { + return f.UserClient, nil +} + func (f *MockScopeFactory) NewVolumeClient() (osclients.VolumeClient, error) { return f.VolumeClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index d9853e381..3fd5d078d 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -153,6 +153,10 @@ func (s *providerScope) NewIdentityClient() (clients.IdentityClient, error) { return clients.NewIdentityClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewUserClient() (clients.UserClient, error) { + return clients.NewUserClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) NewVolumeClient() (clients.VolumeClient, error) { return clients.NewVolumeClient(s.providerClient, s.providerClientOpts) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 8baa7f404..66e5b92f9 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -58,6 +58,7 @@ type Scope interface { NewNetworkClient() (osclients.NetworkClient, error) NewRoleClient() (osclients.RoleClient, error) NewServiceClient() (osclients.ServiceClient, error) + NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) ExtractToken() (*tokens.Token, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 10cedb065..24dfc7294 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -21,6 +21,7 @@ testDirs: - ./internal/controllers/service/tests/ - ./internal/controllers/subnet/tests/ - ./internal/controllers/trunk/tests/ +- ./internal/controllers/user/tests/ - ./internal/controllers/volume/tests/ - ./internal/controllers/volumetype/tests/ timeout: 240 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/user.go b/pkg/clients/applyconfiguration/api/v1alpha1/user.go new file mode 100644 index 000000000..8c7077cc4 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/user.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" +) + +// UserApplyConfiguration represents a declarative configuration of the User type for use +// with apply. +type UserApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *UserSpecApplyConfiguration `json:"spec,omitempty"` + Status *UserStatusApplyConfiguration `json:"status,omitempty"` +} + +// User constructs a declarative configuration of the User type for use with +// apply. +func User(name, namespace string) *UserApplyConfiguration { + b := &UserApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("User") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractUser extracts the applied configuration owned by fieldManager from +// user. If no managedFields are found in user for fieldManager, a +// UserApplyConfiguration 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. +// user must be a unmodified User API object that was retrieved from the Kubernetes API. +// ExtractUser 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 ExtractUser(user *apiv1alpha1.User, fieldManager string) (*UserApplyConfiguration, error) { + return extractUser(user, fieldManager, "") +} + +// ExtractUserStatus is the same as ExtractUser except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractUserStatus(user *apiv1alpha1.User, fieldManager string) (*UserApplyConfiguration, error) { + return extractUser(user, fieldManager, "status") +} + +func extractUser(user *apiv1alpha1.User, fieldManager string, subresource string) (*UserApplyConfiguration, error) { + b := &UserApplyConfiguration{} + err := managedfields.ExtractInto(user, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.User"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(user.Name) + b.WithNamespace(user.Namespace) + + b.WithKind("User") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b UserApplyConfiguration) 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 *UserApplyConfiguration) WithKind(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithAPIVersion(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithName(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithGenerateName(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithNamespace(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithUID(value types.UID) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithResourceVersion(value string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithGeneration(value int64) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithCreationTimestamp(value metav1.Time) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithLabels(entries map[string]string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithAnnotations(entries map[string]string) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithFinalizers(values ...string) *UserApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *UserApplyConfiguration) 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 *UserApplyConfiguration) WithSpec(value *UserSpecApplyConfiguration) *UserApplyConfiguration { + 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 *UserApplyConfiguration) WithStatus(value *UserStatusApplyConfiguration) *UserApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *UserApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *UserApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *UserApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *UserApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/userfilter.go new file mode 100644 index 000000000..3cc89f7ec --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userfilter.go @@ -0,0 +1,52 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// UserFilterApplyConfiguration represents a declarative configuration of the UserFilter type for use +// with apply. +type UserFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// UserFilterApplyConfiguration constructs a declarative configuration of the UserFilter type for use with +// apply. +func UserFilter() *UserFilterApplyConfiguration { + return &UserFilterApplyConfiguration{} +} + +// 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 *UserFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *UserFilterApplyConfiguration { + b.Name = &value + return b +} + +// WithDomainRef sets the DomainRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainRef field is set to the value of the last call. +func (b *UserFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *UserFilterApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/userimport.go new file mode 100644 index 000000000..4497cbde2 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userimport.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 + +// UserImportApplyConfiguration represents a declarative configuration of the UserImport type for use +// with apply. +type UserImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *UserFilterApplyConfiguration `json:"filter,omitempty"` +} + +// UserImportApplyConfiguration constructs a declarative configuration of the UserImport type for use with +// apply. +func UserImport() *UserImportApplyConfiguration { + return &UserImportApplyConfiguration{} +} + +// 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 *UserImportApplyConfiguration) WithID(value string) *UserImportApplyConfiguration { + 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 *UserImportApplyConfiguration) WithFilter(value *UserFilterApplyConfiguration) *UserImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go new file mode 100644 index 000000000..ed4b86a2e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.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" +) + +// UserResourceSpecApplyConfiguration represents a declarative configuration of the UserResourceSpec type for use +// with apply. +type UserResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + DefaultProjectRef *apiv1alpha1.KubernetesNameRef `json:"defaultProjectRef,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +// UserResourceSpecApplyConfiguration constructs a declarative configuration of the UserResourceSpec type for use with +// apply. +func UserResourceSpec() *UserResourceSpecApplyConfiguration { + return &UserResourceSpecApplyConfiguration{} +} + +// 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 *UserResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *UserResourceSpecApplyConfiguration { + 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 *UserResourceSpecApplyConfiguration) WithDescription(value string) *UserResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithDomainRef sets the DomainRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainRef field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *UserResourceSpecApplyConfiguration { + b.DomainRef = &value + return b +} + +// WithDefaultProjectRef sets the DefaultProjectRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultProjectRef field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithDefaultProjectRef(value apiv1alpha1.KubernetesNameRef) *UserResourceSpecApplyConfiguration { + b.DefaultProjectRef = &value + return b +} + +// WithEnabled sets the Enabled field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Enabled field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithEnabled(value bool) *UserResourceSpecApplyConfiguration { + b.Enabled = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go new file mode 100644 index 000000000..05093ff79 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go @@ -0,0 +1,75 @@ +/* +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 + +// UserResourceStatusApplyConfiguration represents a declarative configuration of the UserResourceStatus type for use +// with apply. +type UserResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DomainID *string `json:"domainID,omitempty"` + DefaultProjectID *string `json:"defaultProjectID,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +// UserResourceStatusApplyConfiguration constructs a declarative configuration of the UserResourceStatus type for use with +// apply. +func UserResourceStatus() *UserResourceStatusApplyConfiguration { + return &UserResourceStatusApplyConfiguration{} +} + +// 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 *UserResourceStatusApplyConfiguration) WithName(value string) *UserResourceStatusApplyConfiguration { + 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 *UserResourceStatusApplyConfiguration) WithDescription(value string) *UserResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithDomainID sets the DomainID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainID field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithDomainID(value string) *UserResourceStatusApplyConfiguration { + b.DomainID = &value + return b +} + +// WithDefaultProjectID sets the DefaultProjectID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultProjectID field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithDefaultProjectID(value string) *UserResourceStatusApplyConfiguration { + b.DefaultProjectID = &value + return b +} + +// WithEnabled sets the Enabled field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Enabled field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithEnabled(value bool) *UserResourceStatusApplyConfiguration { + b.Enabled = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go new file mode 100644 index 000000000..fadcb620b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userspec.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" +) + +// UserSpecApplyConfiguration represents a declarative configuration of the UserSpec type for use +// with apply. +type UserSpecApplyConfiguration struct { + Import *UserImportApplyConfiguration `json:"import,omitempty"` + Resource *UserResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// UserSpecApplyConfiguration constructs a declarative configuration of the UserSpec type for use with +// apply. +func UserSpec() *UserSpecApplyConfiguration { + return &UserSpecApplyConfiguration{} +} + +// 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 *UserSpecApplyConfiguration) WithImport(value *UserImportApplyConfiguration) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithResource(value *UserResourceSpecApplyConfiguration) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *UserSpecApplyConfiguration { + 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 *UserSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *UserSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go new file mode 100644 index 000000000..1aae09224 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.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" +) + +// UserStatusApplyConfiguration represents a declarative configuration of the UserStatus type for use +// with apply. +type UserStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *UserResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// UserStatusApplyConfiguration constructs a declarative configuration of the UserStatus type for use with +// apply. +func UserStatus() *UserStatusApplyConfiguration { + return &UserStatusApplyConfiguration{} +} + +// 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 *UserStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *UserStatusApplyConfiguration { + 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 *UserStatusApplyConfiguration) WithID(value string) *UserStatusApplyConfiguration { + 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 *UserStatusApplyConfiguration) WithResource(value *UserResourceStatusApplyConfiguration) *UserStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 87e4f6e86..3a7e6ae0c 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3234,12 +3234,123 @@ var schemaYAML = typed.YAMLObject(`types: - name: segmentationType type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.User + 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.UserSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserStatus + default: {} - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserDataSpec map: fields: - name: secretRef type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserFilter + map: + fields: + - name: domainRef + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceSpec + map: + fields: + - name: defaultProjectRef + type: + scalar: string + - name: description + type: + scalar: string + - name: domainRef + type: + scalar: string + - name: enabled + type: + scalar: boolean + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus + map: + fields: + - name: defaultProjectID + type: + scalar: string + - name: description + type: + scalar: string + - name: domainID + type: + scalar: string + - name: enabled + type: + scalar: boolean + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserSpec + 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.UserImport + - 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.UserResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserStatus + 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.UserResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Volume map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 1b58223cf..0e7f2efb3 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -374,8 +374,22 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.TrunkSubportSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("TrunkSubportStatus"): return &apiv1alpha1.TrunkSubportStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("User"): + return &apiv1alpha1.UserApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("UserDataSpec"): return &apiv1alpha1.UserDataSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserFilter"): + return &apiv1alpha1.UserFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserImport"): + return &apiv1alpha1.UserImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserResourceSpec"): + return &apiv1alpha1.UserResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserResourceStatus"): + return &apiv1alpha1.UserResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserSpec"): + return &apiv1alpha1.UserSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("UserStatus"): + return &apiv1alpha1.UserStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Volume"): return &apiv1alpha1.VolumeApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("VolumeAttachmentStatus"): 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 7c2e4e67d..9842937c0 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -47,6 +47,7 @@ type OpenstackV1alpha1Interface interface { ServicesGetter SubnetsGetter TrunksGetter + UsersGetter VolumesGetter VolumeTypesGetter } @@ -132,6 +133,10 @@ func (c *OpenstackV1alpha1Client) Trunks(namespace string) TrunkInterface { return newTrunks(c, namespace) } +func (c *OpenstackV1alpha1Client) Users(namespace string) UserInterface { + return newUsers(c, namespace) +} + func (c *OpenstackV1alpha1Client) Volumes(namespace string) VolumeInterface { return newVolumes(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 2b7ba89cc..ad71e007f 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 @@ -104,6 +104,10 @@ func (c *FakeOpenstackV1alpha1) Trunks(namespace string) v1alpha1.TrunkInterface return newFakeTrunks(c, namespace) } +func (c *FakeOpenstackV1alpha1) Users(namespace string) v1alpha1.UserInterface { + return newFakeUsers(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Volumes(namespace string) v1alpha1.VolumeInterface { return newFakeVolumes(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go new file mode 100644 index 000000000..c3ac6b668 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_user.go @@ -0,0 +1,49 @@ +/* +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" +) + +// fakeUsers implements UserInterface +type fakeUsers struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.User, *v1alpha1.UserList, *apiv1alpha1.UserApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeUsers(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.UserInterface { + return &fakeUsers{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.User, *v1alpha1.UserList, *apiv1alpha1.UserApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("users"), + v1alpha1.SchemeGroupVersion.WithKind("User"), + func() *v1alpha1.User { return &v1alpha1.User{} }, + func() *v1alpha1.UserList { return &v1alpha1.UserList{} }, + func(dst, src *v1alpha1.UserList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.UserList) []*v1alpha1.User { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.UserList, items []*v1alpha1.User) { 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 e34607a4b..76493df63 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -56,6 +56,8 @@ type SubnetExpansion interface{} type TrunkExpansion interface{} +type UserExpansion interface{} + type VolumeExpansion interface{} type VolumeTypeExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/user.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/user.go new file mode 100644 index 000000000..d2f6659a7 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/user.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" +) + +// UsersGetter has a method to return a UserInterface. +// A group's client should implement this interface. +type UsersGetter interface { + Users(namespace string) UserInterface +} + +// UserInterface has methods to work with User resources. +type UserInterface interface { + Create(ctx context.Context, user *apiv1alpha1.User, opts v1.CreateOptions) (*apiv1alpha1.User, error) + Update(ctx context.Context, user *apiv1alpha1.User, opts v1.UpdateOptions) (*apiv1alpha1.User, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, user *apiv1alpha1.User, opts v1.UpdateOptions) (*apiv1alpha1.User, 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.User, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.UserList, 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.User, err error) + Apply(ctx context.Context, user *applyconfigurationapiv1alpha1.UserApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.User, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, user *applyconfigurationapiv1alpha1.UserApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.User, err error) + UserExpansion +} + +// users implements UserInterface +type users struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.User, *apiv1alpha1.UserList, *applyconfigurationapiv1alpha1.UserApplyConfiguration] +} + +// newUsers returns a Users +func newUsers(c *OpenstackV1alpha1Client, namespace string) *users { + return &users{ + gentype.NewClientWithListAndApply[*apiv1alpha1.User, *apiv1alpha1.UserList, *applyconfigurationapiv1alpha1.UserApplyConfiguration]( + "users", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.User { return &apiv1alpha1.User{} }, + func() *apiv1alpha1.UserList { return &apiv1alpha1.UserList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index c9f62ae9c..da6452ed5 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -62,6 +62,8 @@ type Interface interface { Subnets() SubnetInformer // Trunks returns a TrunkInformer. Trunks() TrunkInformer + // Users returns a UserInformer. + Users() UserInformer // Volumes returns a VolumeInformer. Volumes() VolumeInformer // VolumeTypes returns a VolumeTypeInformer. @@ -174,6 +176,11 @@ func (v *version) Trunks() TrunkInformer { return &trunkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Users returns a UserInformer. +func (v *version) Users() UserInformer { + return &userInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Volumes returns a VolumeInformer. func (v *version) Volumes() VolumeInformer { return &volumeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/user.go b/pkg/clients/informers/externalversions/api/v1alpha1/user.go new file mode 100644 index 000000000..3cdb83f5f --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/user.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" +) + +// UserInformer provides access to a shared informer and lister for +// Users. +type UserInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.UserLister +} + +type userInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewUserInformer constructs a new informer for User 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 NewUserInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredUserInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredUserInformer constructs a new informer for User 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 NewFilteredUserInformer(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().Users(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Users(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Users(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().Users(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.User{}, + resyncPeriod, + indexers, + ) +} + +func (f *userInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredUserInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *userInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.User{}, f.defaultInformer) +} + +func (f *userInformer) Lister() apiv1alpha1.UserLister { + return apiv1alpha1.NewUserLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index a2cd276ae..07b772628 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -91,6 +91,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Subnets().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("trunks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Trunks().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("users"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Users().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("volumes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Volumes().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("volumetypes"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index e2fc3b2d2..705b22ef6 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -170,6 +170,14 @@ type TrunkListerExpansion interface{} // TrunkNamespaceLister. type TrunkNamespaceListerExpansion interface{} +// UserListerExpansion allows custom methods to be added to +// UserLister. +type UserListerExpansion interface{} + +// UserNamespaceListerExpansion allows custom methods to be added to +// UserNamespaceLister. +type UserNamespaceListerExpansion interface{} + // VolumeListerExpansion allows custom methods to be added to // VolumeLister. type VolumeListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/user.go b/pkg/clients/listers/api/v1alpha1/user.go new file mode 100644 index 000000000..363b6a371 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/user.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" +) + +// UserLister helps list Users. +// All objects returned here must be treated as read-only. +type UserLister interface { + // List lists all Users in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.User, err error) + // Users returns an object that can list and get Users. + Users(namespace string) UserNamespaceLister + UserListerExpansion +} + +// userLister implements the UserLister interface. +type userLister struct { + listers.ResourceIndexer[*apiv1alpha1.User] +} + +// NewUserLister returns a new UserLister. +func NewUserLister(indexer cache.Indexer) UserLister { + return &userLister{listers.New[*apiv1alpha1.User](indexer, apiv1alpha1.Resource("user"))} +} + +// Users returns an object that can list and get Users. +func (s *userLister) Users(namespace string) UserNamespaceLister { + return userNamespaceLister{listers.NewNamespaced[*apiv1alpha1.User](s.ResourceIndexer, namespace)} +} + +// UserNamespaceLister helps list and get Users. +// All objects returned here must be treated as read-only. +type UserNamespaceLister interface { + // List lists all Users in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.User, err error) + // Get retrieves the User from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.User, error) + UserNamespaceListerExpansion +} + +// userNamespaceLister implements the UserNamespaceLister +// interface. +type userNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.User] +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 7aa52282e..5512b3f6b 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -29,6 +29,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Service](#service) - [Subnet](#subnet) - [Trunk](#trunk) +- [User](#user) - [Volume](#volume) - [VolumeType](#volumetype) @@ -183,6 +184,7 @@ _Appears in:_ - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) +- [UserSpec](#userspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -1863,6 +1865,7 @@ _Appears in:_ - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) +- [UserSpec](#userspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -1899,6 +1902,7 @@ _Appears in:_ - [ServiceSpec](#servicespec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) +- [UserSpec](#userspec) - [VolumeSpec](#volumespec) - [VolumeTypeSpec](#volumetypespec) @@ -4194,6 +4198,25 @@ _Appears in:_ +#### User + + + +User is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `User` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[UserSpec](#userspec)_ | spec specifies the desired state of the resource. | | | +| `status` _[UserStatus](#userstatus)_ | status defines the observed state of the resource. | | | + + #### UserDataSpec @@ -4212,10 +4235,120 @@ _Appears in:_ | `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the user data for this server. | | MaxLength: 253
MinLength: 1
| +#### UserFilter + + + +UserFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [UserImport](#userimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| + + +#### UserImport + + + +UserImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [UserSpec](#userspec) + +| 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
| +| `filter` _[UserFilter](#userfilter)_ | 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
| + + +#### UserResourceSpec + + + +UserResourceSpec contains the desired state of the resource. + +_Appears in:_ +- [UserSpec](#userspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | | + + +#### UserResourceStatus + + + +UserResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [UserStatus](#userstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
| +| `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
| +| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | | + + +#### UserSpec + +UserSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [User](#user) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[UserImport](#userimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[UserResourceSpec](#userresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | + + +#### UserStatus + + + +UserStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [User](#user) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| +| `resource` _[UserResourceStatus](#userresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | #### Volume From 6d8867cc79ef8821c6db11e1e754f7cf4dc9d2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 9 Mar 2026 15:23:55 +0100 Subject: [PATCH 065/121] Bump go to 1.25.8 Fixes several go vulnerabilities: - GO-2026-4601 - GO-2026-4602 - GO-2026-4603 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5e037287d..32fc80610 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ BUNDLE_IMG ?= bundle:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.29.0 TRIVY_VERSION = 0.49.1 -GO_VERSION ?= 1.25.7 +GO_VERSION ?= 1.25.8 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) From e02e9aa3832460cf118a71eeffd6e001edafcb08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:34:27 +0000 Subject: [PATCH 066/121] :seedling:(deps): Bump github.com/gophercloud/gophercloud/v2 Bumps the all-go-mod-patch-and-minor group with 1 update in the / directory: [github.com/gophercloud/gophercloud/v2](https://github.com/gophercloud/gophercloud). Updates `github.com/gophercloud/gophercloud/v2` from 2.10.0 to 2.11.0 - [Release notes](https://github.com/gophercloud/gophercloud/releases) - [Changelog](https://github.com/gophercloud/gophercloud/blob/v2.11.0/CHANGELOG.md) - [Commits](https://github.com/gophercloud/gophercloud/compare/v2.10.0...v2.11.0) --- updated-dependencies: - dependency-name: github.com/gophercloud/gophercloud/v2 dependency-version: 2.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 8be1030b5..8a10195dc 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/k-orc/openstack-resource-controller/v2 -go 1.24.0 +go 1.25.7 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-logr/logr v1.4.3 github.com/google/go-cmp v0.7.0 - github.com/gophercloud/gophercloud/v2 v2.10.0 + github.com/gophercloud/gophercloud/v2 v2.11.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 github.com/onsi/ginkgo/v2 v2.28.1 github.com/onsi/gomega v1.39.1 diff --git a/go.sum b/go.sum index 6991793ab..cec4f0417 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/v github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gophercloud/gophercloud/v2 v2.10.0 h1:NRadC0aHNvy4iMoFXj5AFiPmut/Sj3hAPAo9B59VMGc= -github.com/gophercloud/gophercloud/v2 v2.10.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= +github.com/gophercloud/gophercloud/v2 v2.11.0 h1:S0Dp8wPE4mSyv7D0/kWGHnkbuKbzHYm4lQh+FcRRDFM= +github.com/gophercloud/gophercloud/v2 v2.11.0/go.mod h1:fai1ZgWxmROxYcEN3SKY0tQF3Uh0DDCAXU9q/xSQK6I= github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 h1:LS70kbNdqoalMwLXEzP9Xb/cYv9UCzWioXaOynxrytc= github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1/go.mod h1:qDhuzCRKi90/Yyl/yEqkg8+qABEvK44LhP0D3GWKGtY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= From 2be8a2e95573edbb5876818934300a91ab717305 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:35:03 +0000 Subject: [PATCH 067/121] :seedling:(deps): Bump docker/setup-buildx-action Bumps the all-github-actions group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `docker/setup-buildx-action` from 3 to 4 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/container_image.yaml | 2 +- .github/workflows/release_image.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index 8a85be043..a8961e2e1 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 fetch-tags: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - run: | docker login -u="${{ secrets.QUAY_USERNAME }}" -p="${{ secrets.QUAY_TOKEN }}" quay.io diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index 791f42d5e..2924d8e73 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 fetch-tags: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Install build dependencies run: sudo apt-get install -y libgpgme-dev From e791581d986c0644de515d3679e954bead2e5d46 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Thu, 19 Feb 2026 16:13:44 +0000 Subject: [PATCH 068/121] network: implement addressscope controller --- PROJECT | 8 + README.md | 1 + api/v1alpha1/addressscope_types.go | 60 ++-- .../zz_generated.addressscope-resource.go | 179 ++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 172 ++++++++- cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 284 ++++++++++++++- cmd/resource-generator/main.go | 3 + .../openstack.k-orc.cloud_addressscopes.yaml | 338 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + .../openstack_v1alpha1_addressscope.yaml | 4 +- internal/controllers/addressscope/actuator.go | 42 +-- .../controllers/addressscope/actuator_test.go | 20 +- internal/controllers/addressscope/status.go | 11 +- .../addressscope-create-full/00-assert.yaml | 5 +- .../00-create-resource.yaml | 15 +- .../tests/addressscope-create-full/README.md | 4 +- .../00-assert.yaml | 5 +- .../00-create-resource.yaml | 6 +- .../addressscope-create-minimal/README.md | 2 +- .../00-create-resources-missing-deps.yaml | 10 +- .../01-create-dependencies.yaml | 4 +- .../00-import-resource.yaml | 4 +- .../01-create-trap-resource.yaml | 9 +- .../02-create-resource.yaml | 7 +- .../addressscope-import-dependency/README.md | 6 +- .../00-create-resources.yaml | 8 +- .../01-import-resource.yaml | 2 +- .../00-import-resource.yaml | 5 +- .../tests/addressscope-import/01-assert.yaml | 4 +- .../01-create-trap-resource.yaml | 7 +- .../tests/addressscope-import/02-assert.yaml | 4 +- .../02-create-resource.yaml | 7 +- .../tests/addressscope-import/README.md | 6 +- .../tests/addressscope-update/00-assert.yaml | 35 +- .../00-minimal-resource.yaml | 6 +- .../00-minimal-shared.yaml | 13 + .../tests/addressscope-update/01-assert.yaml | 21 +- .../01-updated-resource.yaml | 10 +- .../tests/addressscope-update/02-assert.yaml | 13 +- .../tests/addressscope-update/README.md | 5 +- .../addressscope/zz_generated.adapter.go | 88 +++++ .../addressscope/zz_generated.controller.go | 45 +++ internal/osclients/mock/addressscope.go | 131 +++++++ internal/osclients/mock/doc.go | 3 + internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/addressscope.go | 281 +++++++++++++++ .../api/v1alpha1/addressscopefilter.go | 70 ++++ .../api/v1alpha1/addressscopeimport.go | 48 +++ .../api/v1alpha1/addressscoperesourcespec.go | 70 ++++ .../v1alpha1/addressscoperesourcestatus.go | 66 ++++ .../api/v1alpha1/addressscopespec.go | 79 ++++ .../api/v1alpha1/addressscopestatus.go | 66 ++++ .../applyconfiguration/internal/internal.go | 112 ++++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/addressscope.go | 74 ++++ .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_addressscope.go | 53 +++ .../api/v1alpha1/fake/fake_api_client.go | 4 + .../typed/api/v1alpha1/generated_expansion.go | 2 + .../api/v1alpha1/addressscope.go | 102 ++++++ .../api/v1alpha1/interface.go | 7 + .../informers/externalversions/generic.go | 2 + .../listers/api/v1alpha1/addressscope.go | 70 ++++ .../api/v1alpha1/expansion_generated.go | 8 + test/apivalidations/addressscope_test.go | 77 ++++ website/docs/crd-reference.md | 135 +++++++ 71 files changed, 2785 insertions(+), 189 deletions(-) create mode 100644 api/v1alpha1/zz_generated.addressscope-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml create mode 100644 internal/controllers/addressscope/tests/addressscope-update/00-minimal-shared.yaml create mode 100644 internal/controllers/addressscope/zz_generated.adapter.go create mode 100644 internal/controllers/addressscope/zz_generated.controller.go create mode 100644 internal/osclients/mock/addressscope.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscope.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscopefilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscopeimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/addressscope.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_addressscope.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/addressscope.go create mode 100644 pkg/clients/listers/api/v1alpha1/addressscope.go create mode 100644 test/apivalidations/addressscope_test.go diff --git a/PROJECT b/PROJECT index 54fc55950..b3f1540fc 100644 --- a/PROJECT +++ b/PROJECT @@ -8,6 +8,14 @@ layout: projectName: orc repo: github.com/k-orc/openstack-resource-controller resources: +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: AddressScope + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/README.md b/README.md index c05143838..5bddd51c3 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ kubectl delete -f $ORC_RELEASE | **controller** | **1.x** | **2.x** | **main** | |:---------------------------:|:-------:|:-------:|:--------:| +| addressscope | | ✔ | ✔ | | domain | | ✔ | ✔ | | endpoint | | ◐ | ◐ | | flavor | | ✔ | ✔ | diff --git a/api/v1alpha1/addressscope_types.go b/api/v1alpha1/addressscope_types.go index d7f47e804..8a28e4585 100644 --- a/api/v1alpha1/addressscope_types.go +++ b/api/v1alpha1/addressscope_types.go @@ -23,24 +23,23 @@ type AddressScopeResourceSpec struct { // +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"` - // 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/layer3/addressscopes - // - // 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"` + // ipVersion is the IP protocol version. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ipVersion is immutable" + IPVersion IPVersion `json:"ipVersion"` + + // shared indicates whether this resource is shared across all + // projects or not. By default, only admin users can change set + // this value. We can't unshared a shared address scope; Neutron + // enforces this. + // +optional + // +kubebuilder:validation:XValidation:rule="!(oldSelf && !self)",message="shared address scope can't be unshared" + Shared *bool `json:"shared,omitempty"` } // AddressScopeFilter defines an existing resource by its properties @@ -50,19 +49,19 @@ type AddressScopeFilter struct { // +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"` - // 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/layer3/addressscopes + // ipVersion is the IP protocol version. + // +optional + IPVersion IPVersion `json:"ipVersion,omitempty"` + + // shared indicates whether this resource is shared across all + // projects or not. By default, only admin users can change set + // this value. + // +optional + Shared *bool `json:"shared,omitempty"` } // AddressScopeResourceStatus represents the observed state of the resource. @@ -72,17 +71,18 @@ type AddressScopeResourceStatus struct { // +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"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the AddressScope structure from - // github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/addressscopes + // ipVersion is the IP protocol version. + // +optional + IPVersion int32 `json:"ipVersion,omitempty"` + + // shared indicates whether this resource is shared across all + // projects or not. By default, only admin users can change set + // this value. + // +optional + Shared *bool `json:"shared,omitempty"` } diff --git a/api/v1alpha1/zz_generated.addressscope-resource.go b/api/v1alpha1/zz_generated.addressscope-resource.go new file mode 100644 index 000000000..a61636c7d --- /dev/null +++ b/api/v1alpha1/zz_generated.addressscope-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" +) + +// AddressScopeImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type AddressScopeImport 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 *AddressScopeFilter `json:"filter,omitempty"` +} + +// AddressScopeSpec 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 AddressScopeSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *AddressScopeImport `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 *AddressScopeResourceSpec `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"` +} + +// AddressScopeStatus defines the observed state of an ORC resource. +type AddressScopeStatus 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 *AddressScopeResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &AddressScope{} + +func (i *AddressScope) 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" + +// AddressScope is the Schema for an ORC resource. +type AddressScope 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 AddressScopeSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status AddressScopeStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// AddressScopeList contains a list of AddressScope. +type AddressScopeList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of AddressScope. + // +required + Items []AddressScope `json:"items"` +} + +func (l *AddressScopeList) GetItems() []AddressScope { + return l.Items +} + +func init() { + SchemeBuilder.Register(&AddressScope{}, &AddressScopeList{}) +} + +func (i *AddressScope) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &AddressScope{} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bc18dc6d0..c2ef6bb40 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -45,6 +45,33 @@ func (in *Address) DeepCopy() *Address { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScope) DeepCopyInto(out *AddressScope) { + *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 AddressScope. +func (in *AddressScope) DeepCopy() *AddressScope { + if in == nil { + return nil + } + out := new(AddressScope) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AddressScope) 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 *AddressScopeFilter) DeepCopyInto(out *AddressScopeFilter) { *out = *in @@ -53,16 +80,16 @@ func (in *AddressScopeFilter) DeepCopyInto(out *AddressScopeFilter) { *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.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeFilter. @@ -75,6 +102,63 @@ func (in *AddressScopeFilter) DeepCopy() *AddressScopeFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeImport) DeepCopyInto(out *AddressScopeImport) { + *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(AddressScopeFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeImport. +func (in *AddressScopeImport) DeepCopy() *AddressScopeImport { + if in == nil { + return nil + } + out := new(AddressScopeImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeList) DeepCopyInto(out *AddressScopeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AddressScope, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeList. +func (in *AddressScopeList) DeepCopy() *AddressScopeList { + if in == nil { + return nil + } + out := new(AddressScopeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AddressScopeList) 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 *AddressScopeResourceSpec) DeepCopyInto(out *AddressScopeResourceSpec) { *out = *in @@ -83,16 +167,16 @@ func (in *AddressScopeResourceSpec) DeepCopyInto(out *AddressScopeResourceSpec) *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.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeResourceSpec. @@ -108,6 +192,11 @@ func (in *AddressScopeResourceSpec) DeepCopy() *AddressScopeResourceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AddressScopeResourceStatus) DeepCopyInto(out *AddressScopeResourceStatus) { *out = *in + if in.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeResourceStatus. @@ -120,6 +209,69 @@ func (in *AddressScopeResourceStatus) DeepCopy() *AddressScopeResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeSpec) DeepCopyInto(out *AddressScopeSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(AddressScopeImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(AddressScopeResourceSpec) + (*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 AddressScopeSpec. +func (in *AddressScopeSpec) DeepCopy() *AddressScopeSpec { + if in == nil { + return nil + } + out := new(AddressScopeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AddressScopeStatus) DeepCopyInto(out *AddressScopeStatus) { + *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(AddressScopeResourceStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeStatus. +func (in *AddressScopeStatus) DeepCopy() *AddressScopeStatus { + if in == nil { + return nil + } + out := new(AddressScopeStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AllocationPool) DeepCopyInto(out *AllocationPool) { *out = *in diff --git a/cmd/manager/main.go b/cmd/manager/main.go index bb5b27c69..0da3b3bb6 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -27,6 +27,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/addressscope" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/domain" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/endpoint" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/flavor" @@ -109,6 +110,7 @@ func main() { scopeFactory := scope.NewFactory(orcOpts.ScopeCacheMaxSize, caCerts) controllers := []interfaces.Controller{ + addressscope.New(scopeFactory), endpoint.New(scopeFactory), image.New(scopeFactory), network.New(scopeFactory), diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 81b3df785..372fc02ea 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -31,9 +31,14 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Address": schema_openstack_resource_controller_v2_api_v1alpha1_Address(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScope": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScope(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeImport": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeList": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeList(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllocationPool": schema_openstack_resource_controller_v2_api_v1alpha1_AllocationPool(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllocationPoolStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllocationPoolStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPair(ref), @@ -564,6 +569,57 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Address(ref common.Ref } } +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScope(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScope 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.AddressScopeSpec"), + }, + }, + "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.AddressScopeStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -578,23 +634,109 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeFilter(ref Format: "", }, }, - "description": { + "projectRef": { SchemaProps: spec.SchemaProps{ - Description: "description of the existing resource", + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", Type: []string{"string"}, Format: "", }, }, - "projectRef": { + "ipVersion": { SchemaProps: spec.SchemaProps{ - Description: "projectRef is a reference to the ORC Project which this resource is associated with.", + Description: "ipVersion is the IP protocol version.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "shared": { + SchemaProps: spec.SchemaProps{ + Description: "shared indicates whether this resource is shared across all projects or not. By default, only admin users can change set this value.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeImport 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.AddressScopeFilter"), + }, + }, }, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeList contains a list of AddressScope.", + 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 AddressScope.", + 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.AddressScope"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScope", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } @@ -612,21 +754,30 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceSp Format: "", }, }, - "description": { + "projectRef": { SchemaProps: spec.SchemaProps{ - Description: "description is a human-readable description for the resource.", + Description: "projectRef is a reference to the ORC Project which this resource is associated with.", Type: []string{"string"}, Format: "", }, }, - "projectRef": { + "ipVersion": { SchemaProps: spec.SchemaProps{ - Description: "projectRef is a reference to the ORC Project which this resource is associated with.", - Type: []string{"string"}, + Description: "ipVersion is the IP protocol version.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "shared": { + SchemaProps: spec.SchemaProps{ + Description: "shared indicates whether this resource is shared across all projects or not. By default, only admin users can change set this value. We can't unshared a shared address scope; Neutron enforces this.", + Type: []string{"boolean"}, Format: "", }, }, }, + Required: []string{"ipVersion"}, }, }, } @@ -646,23 +797,130 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeResourceSt Format: "", }, }, - "description": { + "projectID": { SchemaProps: spec.SchemaProps{ - Description: "description is a human-readable description for the resource.", + Description: "projectID is the ID of the Project to which the resource is associated.", Type: []string{"string"}, Format: "", }, }, - "projectID": { + "ipVersion": { SchemaProps: spec.SchemaProps{ - Description: "projectID is the ID of the Project to which the resource is associated.", + Description: "ipVersion is the IP protocol version.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "shared": { + SchemaProps: spec.SchemaProps{ + Description: "shared indicates whether this resource is shared across all projects or not. By default, only admin users can change set this value.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeSpec 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.AddressScopeImport"), + }, + }, + "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.AddressScopeResourceSpec"), + }, + }, + "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.AddressScopeImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AddressScopeStatus 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.AddressScopeResourceStatus"), + }, + }, }, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 0e7444cc1..147ec0edc 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -168,6 +168,9 @@ var resources []templateFields = []templateFields{ Name: "Endpoint", IsNotNamed: true, }, + { + Name: "AddressScope", + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml b/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml new file mode 100644 index 000000000..11fd4e11f --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml @@ -0,0 +1,338 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: addressscopes.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: AddressScope + listKind: AddressScopeList + plural: addressscopes + singular: addressscope + 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: AddressScope 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: + ipVersion: + description: ipVersion is the IP protocol version. + enum: + - 4 + - 6 + format: int32 + type: integer + 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 + shared: + description: |- + shared indicates whether this resource is shared across all + projects or not. By default, only admin users can change set + this value. + type: boolean + 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: + ipVersion: + description: ipVersion is the IP protocol version. + enum: + - 4 + - 6 + format: int32 + type: integer + x-kubernetes-validations: + - message: ipVersion is immutable + rule: self == oldSelf + name: + description: |- + name will be the name of the created resource. If not specified, the + name of the ORC object will be used. + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + projectRef: + description: projectRef is a reference to the ORC Project which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: projectRef is immutable + rule: self == oldSelf + shared: + description: |- + shared indicates whether this resource is shared across all + projects or not. By default, only admin users can change set + this value. We can't unshared a shared address scope; Neutron + enforces this. + type: boolean + x-kubernetes-validations: + - message: shared address scope can't be unshared + rule: '!(oldSelf && !self)' + required: + - ipVersion + 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: + ipVersion: + description: ipVersion is the IP protocol version. + format: int32 + type: integer + 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 + shared: + description: |- + shared indicates whether this resource is shared across all + projects or not. By default, only admin users can change set + this value. + type: boolean + 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 319e67e8a..196a5c030 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: +- bases/openstack.k-orc.cloud_addressscopes.yaml - bases/openstack.k-orc.cloud_domains.yaml - bases/openstack.k-orc.cloud_endpoints.yaml - bases/openstack.k-orc.cloud_flavors.yaml diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 488fa1eb7..3f68a903d 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,6 +1,7 @@ # Code generated by resource-generator. DO NOT EDIT. ## Append samples of your project ## resources: +- openstack_v1alpha1_addressscope.yaml - openstack_v1alpha1_domain.yaml - openstack_v1alpha1_endpoint.yaml - openstack_v1alpha1_flavor.yaml diff --git a/config/samples/openstack_v1alpha1_addressscope.yaml b/config/samples/openstack_v1alpha1_addressscope.yaml index 9647f7d75..16435fac3 100644 --- a/config/samples/openstack_v1alpha1_addressscope.yaml +++ b/config/samples/openstack_v1alpha1_addressscope.yaml @@ -5,10 +5,8 @@ metadata: name: addressscope-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 AddressScope - # TODO(scaffolding): Add all fields the resource supports + ipVersion: 4 diff --git a/internal/controllers/addressscope/actuator.go b/internal/controllers/addressscope/actuator.go index 8504f474d..dd062fe9f 100644 --- a/internal/controllers/addressscope/actuator.go +++ b/internal/controllers/addressscope/actuator.go @@ -71,22 +71,14 @@ func (actuator addressscopeActuator) ListOSResourcesForAdoption(ctx context.Cont return nil, false } - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter - listOpts := addressscopes.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Name: getResourceName(orcObject), } return actuator.osClient.ListAddressScopes(ctx, listOpts), true } func (actuator addressscopeActuator) 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( @@ -101,10 +93,10 @@ func (actuator addressscopeActuator) ListOSResourcesForImport(ctx context.Contex } listOpts := addressscopes.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - ProjectID: ptr.Deref(project.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), + ProjectID: ptr.Deref(project.Status.ID, ""), + IPVersion: int(filter.IPVersion), + Shared: filter.Shared, } return actuator.osClient.ListAddressScopes(ctx, listOpts), reconcileStatus @@ -135,11 +127,15 @@ func (actuator addressscopeActuator) CreateResource(ctx context.Context, obj orc if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } + createOpts := addressscopes.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - ProjectID: projectID, - // TODO(scaffolding): Add more fields + Name: getResourceName(obj), + ProjectID: projectID, + IPVersion: int(resource.IPVersion), + } + + if resource.Shared != nil { + createOpts.Shared = *resource.Shared } osResource, err := actuator.osClient.CreateAddressScope(ctx, createOpts) @@ -170,9 +166,7 @@ func (actuator addressscopeActuator) updateResource(ctx context.Context, obj orc updateOpts := addressscopes.UpdateOpts{} handleNameUpdate(&updateOpts, obj, osResource) - handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability + handleSharedUpdate(&updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -219,10 +213,10 @@ func handleNameUpdate(updateOpts *addressscopes.UpdateOpts, obj orcObjectPT, osR } } -func handleDescriptionUpdate(updateOpts *addressscopes.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - description := ptr.Deref(resource.Description, "") - if osResource.Description != description { - updateOpts.Description = &description +func handleSharedUpdate(updateOpts *addressscopes.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + shared := ptr.Deref(resource.Shared, false) + if shared != osResource.Shared { + updateOpts.Shared = &shared } } diff --git a/internal/controllers/addressscope/actuator_test.go b/internal/controllers/addressscope/actuator_test.go index dd3cabaab..151595fb4 100644 --- a/internal/controllers/addressscope/actuator_test.go +++ b/internal/controllers/addressscope/actuator_test.go @@ -87,27 +87,25 @@ func TestHandleNameUpdate(t *testing.T) { } } -func TestHandleDescriptionUpdate(t *testing.T) { - ptrToDescription := ptr.To[string] +func TestHandleSharedUpdate(t *testing.T) { testCases := []struct { name string - newValue *string - existingValue string + newValue *bool + existingValue bool 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}, + {name: "Identical true", newValue: ptr.To(true), existingValue: true, expectChange: false}, + {name: "Identical false", newValue: ptr.To(false), existingValue: false, expectChange: false}, + {name: "Change from false to true", newValue: ptr.To(true), existingValue: false, expectChange: true}, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.AddressScopeResourceSpec{Description: tt.newValue} - osResource := &osResourceT{Description: tt.existingValue} + resource := &orcv1alpha1.AddressScopeResourceSpec{Shared: tt.newValue} + osResource := &osResourceT{Shared: tt.existingValue} updateOpts := addressscopes.UpdateOpts{} - handleDescriptionUpdate(&updateOpts, resource, osResource) + handleSharedUpdate(&updateOpts, resource, osResource) got, _ := needsUpdate(updateOpts) if got != tt.expectChange { diff --git a/internal/controllers/addressscope/status.go b/internal/controllers/addressscope/status.go index d64cc6520..5065adfad 100644 --- a/internal/controllers/addressscope/status.go +++ b/internal/controllers/addressscope/status.go @@ -51,14 +51,9 @@ func (addressscopeStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.A func (addressscopeStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.AddressScopeResourceStatus(). WithProjectID(osResource.ProjectID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the AddressScopeResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional - - if osResource.Description != "" { - resourceStatus.WithDescription(osResource.Description) - } + WithName(osResource.Name). + WithShared(osResource.Shared). + WithIPVersion(int32(osResource.IPVersion)) statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml index 752cf81ca..00cce7ced 100644 --- a/internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-create-full/00-assert.yaml @@ -6,8 +6,8 @@ metadata: status: resource: name: addressscope-create-full-override - description: AddressScope from "create full" test - # TODO(scaffolding): Add all fields the resource supports + ipVersion: 4 + shared: true conditions: - type: Available status: "True" @@ -30,4 +30,3 @@ resourceRefs: assertAll: - celExpr: "addressscope.status.id != ''" - celExpr: "addressscope.status.resource.projectID == project.status.id" - # TODO(scaffolding): Add more checks diff --git a/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml index 93adb07a2..cd6928ae9 100644 --- a/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-create-full/00-create-resource.yaml @@ -5,11 +5,9 @@ metadata: name: addressscope-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,15 @@ metadata: name: addressscope-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + # We need to use admin credentials to be able to create this + # AddressScope because we're specifying a different project + # that we are authenticated. + # https://docs.openstack.org/api-ref/network/v2/index.html#create-address-scope + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: name: addressscope-create-full-override - description: AddressScope from "create full" test projectRef: addressscope-create-full - # TODO(scaffolding): Add all fields the resource supports + ipVersion: 4 + shared: true diff --git a/internal/controllers/addressscope/tests/addressscope-create-full/README.md b/internal/controllers/addressscope/tests/addressscope-create-full/README.md index 2dcbd470d..b1559172a 100644 --- a/internal/controllers/addressscope/tests/addressscope-create-full/README.md +++ b/internal/controllers/addressscope/tests/addressscope-create-full/README.md @@ -1,8 +1,8 @@ -# Create a AddressScope with all the options +# Create an AddressScope with all the options ## Step 00 -Create a AddressScope using all available fields, and verify that the observed state corresponds to the spec. +Create an AddressScope 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. diff --git a/internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml index bfc03fc62..667713d5a 100644 --- a/internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-assert.yaml @@ -6,7 +6,8 @@ metadata: status: resource: name: addressscope-create-minimal - # TODO(scaffolding): Add all fields the resource supports + ipVersion: 4 + shared: false conditions: - type: Available status: "True" @@ -24,4 +25,4 @@ resourceRefs: ref: addressscope assertAll: - celExpr: "addressscope.status.id != ''" - # TODO(scaffolding): Add more checks + - celExpr: "addressscope.status.resource.projectID != ''" diff --git a/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml index c3909c87f..8e3f088e5 100644 --- a/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-create-minimal/00-create-resource.yaml @@ -5,10 +5,8 @@ metadata: name: addressscope-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: + ipVersion: 4 diff --git a/internal/controllers/addressscope/tests/addressscope-create-minimal/README.md b/internal/controllers/addressscope/tests/addressscope-create-minimal/README.md index ab132b070..c60142ac7 100644 --- a/internal/controllers/addressscope/tests/addressscope-create-minimal/README.md +++ b/internal/controllers/addressscope/tests/addressscope-create-minimal/README.md @@ -1,4 +1,4 @@ -# Create a AddressScope with the minimum options +# Create an AddressScope with the minimum options ## Step 00 diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml index d73eb617c..e731f2549 100644 --- a/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/addressscope/tests/addressscope-dependency/00-create-resources-missing-deps.yaml @@ -5,13 +5,12 @@ metadata: name: addressscope-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: addressscope-dependency - # TODO(scaffolding): Add the necessary fields to create the resource + ipVersion: 4 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: AddressScope @@ -19,9 +18,8 @@ metadata: name: addressscope-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: addressscope-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + ipVersion: 4 diff --git a/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml b/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml index 9fb1fa9d5..6cd0d5040 100644 --- a/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml +++ b/internal/controllers/addressscope/tests/addressscope-dependency/01-create-dependencies.yaml @@ -11,9 +11,7 @@ metadata: name: addressscope-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: {} diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml index 61d1c1ba3..f5e9a0b58 100644 --- a/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/00-import-resource.yaml @@ -5,7 +5,7 @@ metadata: name: addressscope-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -18,7 +18,7 @@ metadata: name: addressscope-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml index 0e7f68c0a..0c8fcf0aa 100644 --- a/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/01-create-trap-resource.yaml @@ -5,11 +5,9 @@ metadata: name: addressscope-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: {} --- # This `addressscope-import-dependency-not-this-one` should not be picked by the import filter @@ -19,10 +17,9 @@ metadata: name: addressscope-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: + ipVersion: 4 projectRef: addressscope-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml index 467ab6261..3ca9845d6 100644 --- a/internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/02-create-resource.yaml @@ -5,11 +5,9 @@ metadata: name: addressscope-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,10 +16,9 @@ metadata: name: addressscope-import-dependency-external spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: + ipVersion: 4 projectRef: addressscope-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md b/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md index a4257bfc1..ce4b071ca 100644 --- a/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md +++ b/internal/controllers/addressscope/tests/addressscope-import-dependency/README.md @@ -2,16 +2,16 @@ ## Step 00 -Import a AddressScope that references other imported resources. The referenced imported resources have no matching resources yet. +Import an AddressScope that references other imported resources. The referenced imported resources have no matching resources yet. Verify the AddressScope is waiting for the dependency to be ready. ## Step 01 -Create a AddressScope matching the import filter, except for referenced resources, and verify that it's not being imported. +Create an AddressScope matching the import filter, except for referenced resources, and verify that it's not being imported. ## Step 02 -Create the referenced resources and a AddressScope matching the import filters. +Create the referenced resources and an AddressScope matching the import filters. Verify that the observed status on the imported AddressScope corresponds to the spec of the created AddressScope. diff --git a/internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml index f8973e7e7..775ce0239 100644 --- a/internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import-error/00-create-resources.yaml @@ -5,13 +5,11 @@ metadata: name: addressscope-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: AddressScope from "import error" test - # TODO(scaffolding): add any required field + ipVersion: 4 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: AddressScope @@ -19,10 +17,8 @@ metadata: name: addressscope-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: AddressScope from "import error" test - # TODO(scaffolding): add any required field + ipVersion: 4 diff --git a/internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml index 3968af498..c9073e8a6 100644 --- a/internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import-error/01-import-resource.yaml @@ -10,4 +10,4 @@ spec: managementPolicy: unmanaged import: filter: - description: AddressScope from "import error" test + ipVersion: 4 diff --git a/internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml index 385c33cb1..d25ab6f94 100644 --- a/internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import/00-import-resource.yaml @@ -11,5 +11,6 @@ spec: import: filter: name: addressscope-import-external - description: AddressScope addressscope-import-external from "addressscope-import" test - # TODO(scaffolding): Add all fields supported by the filter + ipVersion: 4 + shared: true + diff --git a/internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml index 6f4897ab2..1f6fed6d5 100644 --- a/internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import/01-assert.yaml @@ -15,8 +15,8 @@ status: reason: Success resource: name: addressscope-import-external-not-this-one - description: AddressScope addressscope-import-external from "addressscope-import" test - # TODO(scaffolding): Add fields necessary to match filter + ipVersion: 4 + shared: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: AddressScope diff --git a/internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml index 7661c9e14..cffc38ecd 100644 --- a/internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import/01-create-trap-resource.yaml @@ -8,10 +8,9 @@ metadata: name: addressscope-import-external-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: - description: AddressScope addressscope-import-external from "addressscope-import" test - # TODO(scaffolding): Add fields necessary to match filter + ipVersion: 4 + shared: true diff --git a/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml b/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml index 9b2f882d9..0e7ecfc38 100644 --- a/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import/02-assert.yaml @@ -29,5 +29,5 @@ status: reason: Success resource: name: addressscope-import-external - description: AddressScope addressscope-import-external from "addressscope-import" test - # TODO(scaffolding): Add all fields the resource supports + ipVersion: 4 + shared: true diff --git a/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml b/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml index b9ea33592..3e81b0d9b 100644 --- a/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-import/02-create-resource.yaml @@ -5,10 +5,9 @@ metadata: name: addressscope-import-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 resource: - description: AddressScope addressscope-import-external from "addressscope-import" test - # TODO(scaffolding): Add fields necessary to match filter + ipVersion: 4 + shared: true diff --git a/internal/controllers/addressscope/tests/addressscope-import/README.md b/internal/controllers/addressscope/tests/addressscope-import/README.md index 59f54261e..54359a58b 100644 --- a/internal/controllers/addressscope/tests/addressscope-import/README.md +++ b/internal/controllers/addressscope/tests/addressscope-import/README.md @@ -2,15 +2,15 @@ ## Step 00 -Import a addressscope that matches all fields in the filter, and verify it is waiting for the external resource to be created. +Import an addressscope that matches all fields in the filter, and verify it is waiting for the external resource to be created. ## Step 01 -Create a addressscope 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. +Create an addressscope 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 addressscope matching the filter and verify that the observed status on the imported addressscope corresponds to the spec of the created addressscope. +Create an addressscope matching the filter and verify that the observed status on the imported addressscope corresponds to the spec of the created addressscope. Also, confirm that it does not adopt any addressscope whose name is a superstring of its own. ## Reference diff --git a/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml b/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml index b9ba9d3a8..5bf475fa1 100644 --- a/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-update/00-assert.yaml @@ -2,12 +2,17 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: AddressScope - name: addressscope-update - ref: addressscope + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-update + ref: addressscope + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-update-shared + ref: addressscopeShared assertAll: - - celExpr: "!has(addressscope.status.resource.description)" + - celExpr: "addressscope.status.resource.projectID != ''" + - celExpr: "addressscopeShared.status.resource.projectID != ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: AddressScope @@ -16,7 +21,25 @@ metadata: status: resource: name: addressscope-update - # TODO(scaffolding): Add matches for more fields + ipVersion: 4 + shared: false + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update-shared +status: + resource: + name: addressscope-update-shared + ipVersion: 4 + shared: false conditions: - type: Available status: "True" diff --git a/internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml b/internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml index 994c24cb5..bf3092497 100644 --- a/internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-update/00-minimal-resource.yaml @@ -5,10 +5,8 @@ metadata: name: addressscope-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: + ipVersion: 4 diff --git a/internal/controllers/addressscope/tests/addressscope-update/00-minimal-shared.yaml b/internal/controllers/addressscope/tests/addressscope-update/00-minimal-shared.yaml new file mode 100644 index 000000000..bf345b0bb --- /dev/null +++ b/internal/controllers/addressscope/tests/addressscope-update/00-minimal-shared.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update-shared +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + ipVersion: 4 + shared: false diff --git a/internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml b/internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml index 3caafcc31..ca3ae4d37 100644 --- a/internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-update/01-assert.yaml @@ -6,8 +6,25 @@ metadata: status: resource: name: addressscope-update-updated - description: addressscope-update-updated - # TODO(scaffolding): match all fields that were modified + ipVersion: 4 + shared: false + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update-shared +status: + resource: + name: addressscope-update-shared + ipVersion: 4 + shared: true conditions: - type: Available status: "True" diff --git a/internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml b/internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml index 07624e3e2..aefd7d703 100644 --- a/internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml +++ b/internal/controllers/addressscope/tests/addressscope-update/01-updated-resource.yaml @@ -6,5 +6,11 @@ metadata: spec: resource: name: addressscope-update-updated - description: addressscope-update-updated - # TODO(scaffolding): update all mutable fields +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: AddressScope +metadata: + name: addressscope-update-shared +spec: + resource: + shared: true diff --git a/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml b/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml index c74b5ff06..d095fd69f 100644 --- a/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml +++ b/internal/controllers/addressscope/tests/addressscope-update/02-assert.yaml @@ -2,12 +2,12 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: AddressScope - name: addressscope-update - ref: addressscope + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: AddressScope + name: addressscope-update + ref: addressscope assertAll: - - celExpr: "!has(addressscope.status.resource.description)" + - celExpr: "addressscope.status.resource.projectID != ''" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: AddressScope @@ -16,7 +16,8 @@ metadata: status: resource: name: addressscope-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value + ipVersion: 4 + shared: false conditions: - type: Available status: "True" diff --git a/internal/controllers/addressscope/tests/addressscope-update/README.md b/internal/controllers/addressscope/tests/addressscope-update/README.md index f2f56c33f..8a88a9a3e 100644 --- a/internal/controllers/addressscope/tests/addressscope-update/README.md +++ b/internal/controllers/addressscope/tests/addressscope-update/README.md @@ -2,7 +2,8 @@ ## Step 00 -Create a AddressScope using only mandatory fields. +Create two AddressScopes using only mandatory fields, but one of them +will be used to update the `shared` field. ## Step 01 @@ -10,7 +11,7 @@ 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. +Revert the resource to its original value and verify that the resulting object matches its state when first created, except the resource with the shared field. ## Reference diff --git a/internal/controllers/addressscope/zz_generated.adapter.go b/internal/controllers/addressscope/zz_generated.adapter.go new file mode 100644 index 000000000..768861dbd --- /dev/null +++ b/internal/controllers/addressscope/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 addressscope + +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.AddressScope + orcObjectListT = orcv1alpha1.AddressScopeList + resourceSpecT = orcv1alpha1.AddressScopeResourceSpec + filterT = orcv1alpha1.AddressScopeFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = addressscopeAdapter +) + +type addressscopeAdapter struct { + *orcv1alpha1.AddressScope +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.AddressScope +} + +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/addressscope/zz_generated.controller.go b/internal/controllers/addressscope/zz_generated.controller.go new file mode 100644 index 000000000..c17697f8c --- /dev/null +++ b/internal/controllers/addressscope/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 addressscope + +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/addressscope.go b/internal/osclients/mock/addressscope.go new file mode 100644 index 000000000..fbaf844bb --- /dev/null +++ b/internal/osclients/mock/addressscope.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: ../addressscope.go +// +// Generated by this command: +// +// mockgen -package mock -destination=addressscope.go -source=../addressscope.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock AddressScopeClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + addressscopes "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/addressscopes" + gomock "go.uber.org/mock/gomock" +) + +// MockAddressScopeClient is a mock of AddressScopeClient interface. +type MockAddressScopeClient struct { + ctrl *gomock.Controller + recorder *MockAddressScopeClientMockRecorder + isgomock struct{} +} + +// MockAddressScopeClientMockRecorder is the mock recorder for MockAddressScopeClient. +type MockAddressScopeClientMockRecorder struct { + mock *MockAddressScopeClient +} + +// NewMockAddressScopeClient creates a new mock instance. +func NewMockAddressScopeClient(ctrl *gomock.Controller) *MockAddressScopeClient { + mock := &MockAddressScopeClient{ctrl: ctrl} + mock.recorder = &MockAddressScopeClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAddressScopeClient) EXPECT() *MockAddressScopeClientMockRecorder { + return m.recorder +} + +// CreateAddressScope mocks base method. +func (m *MockAddressScopeClient) CreateAddressScope(ctx context.Context, opts addressscopes.CreateOptsBuilder) (*addressscopes.AddressScope, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateAddressScope", ctx, opts) + ret0, _ := ret[0].(*addressscopes.AddressScope) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateAddressScope indicates an expected call of CreateAddressScope. +func (mr *MockAddressScopeClientMockRecorder) CreateAddressScope(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAddressScope", reflect.TypeOf((*MockAddressScopeClient)(nil).CreateAddressScope), ctx, opts) +} + +// DeleteAddressScope mocks base method. +func (m *MockAddressScopeClient) DeleteAddressScope(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAddressScope", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAddressScope indicates an expected call of DeleteAddressScope. +func (mr *MockAddressScopeClientMockRecorder) DeleteAddressScope(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAddressScope", reflect.TypeOf((*MockAddressScopeClient)(nil).DeleteAddressScope), ctx, resourceID) +} + +// GetAddressScope mocks base method. +func (m *MockAddressScopeClient) GetAddressScope(ctx context.Context, resourceID string) (*addressscopes.AddressScope, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAddressScope", ctx, resourceID) + ret0, _ := ret[0].(*addressscopes.AddressScope) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAddressScope indicates an expected call of GetAddressScope. +func (mr *MockAddressScopeClientMockRecorder) GetAddressScope(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddressScope", reflect.TypeOf((*MockAddressScopeClient)(nil).GetAddressScope), ctx, resourceID) +} + +// ListAddressScopes mocks base method. +func (m *MockAddressScopeClient) ListAddressScopes(ctx context.Context, listOpts addressscopes.ListOptsBuilder) iter.Seq2[*addressscopes.AddressScope, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAddressScopes", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*addressscopes.AddressScope, error]) + return ret0 +} + +// ListAddressScopes indicates an expected call of ListAddressScopes. +func (mr *MockAddressScopeClientMockRecorder) ListAddressScopes(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAddressScopes", reflect.TypeOf((*MockAddressScopeClient)(nil).ListAddressScopes), ctx, listOpts) +} + +// UpdateAddressScope mocks base method. +func (m *MockAddressScopeClient) UpdateAddressScope(ctx context.Context, id string, opts addressscopes.UpdateOptsBuilder) (*addressscopes.AddressScope, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateAddressScope", ctx, id, opts) + ret0, _ := ret[0].(*addressscopes.AddressScope) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateAddressScope indicates an expected call of UpdateAddressScope. +func (mr *MockAddressScopeClientMockRecorder) UpdateAddressScope(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAddressScope", reflect.TypeOf((*MockAddressScopeClient)(nil).UpdateAddressScope), ctx, id, opts) +} diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 176f3bc93..206e6752f 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -35,6 +35,9 @@ import ( //go:generate mockgen -package mock -destination=identity.go -source=../identity.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock IdentityClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt identity.go > _identity.go && mv _identity.go identity.go" +//go:generate mockgen -package mock -destination=addressscope.go -source=../addressscope.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock AddressScopeClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt addressscope.go > _addressscope.go && mv _addressscope.go addressscope.go" + //go:generate mockgen -package mock -destination=domain.go -source=../domain.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock DomainClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt domain.go > _domain.go && mv _domain.go domain.go" diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 9cc49cd03..67c8744d3 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -34,6 +34,7 @@ import ( // MockScopeFactory implements both the ScopeFactory and ClientScope interfaces. It can be used in place of the default ProviderScopeFactory // when we want to use mocked service clients which do not attempt to connect to a running OpenStack cloud. type MockScopeFactory struct { + AddressScope *mock.MockAddressScopeClient ComputeClient *mock.MockComputeClient DomainClient *mock.MockDomainClient EndpointClient *mock.MockEndpointClient @@ -51,6 +52,7 @@ type MockScopeFactory struct { } func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { + addressScope := mock.NewMockAddressScopeClient(mockCtrl) computeClient := mock.NewMockComputeClient(mockCtrl) domainClient := mock.NewMockDomainClient(mockCtrl) endpointClient := mock.NewMockEndpointClient(mockCtrl) @@ -65,6 +67,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) return &MockScopeFactory{ + AddressScope: addressScope, ComputeClient: computeClient, DomainClient: domainClient, EndpointClient: endpointClient, @@ -91,6 +94,10 @@ func (f *MockScopeFactory) NewClientScopeFromObject(_ context.Context, _ client. return f, nil } +func (f *MockScopeFactory) NewAddressScopeClient() (osclients.AddressScopeClient, error) { + return f.AddressScope, nil +} + func (f *MockScopeFactory) NewComputeClient() (osclients.ComputeClient, error) { return f.ComputeClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index d9853e381..f9ff9f88f 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -137,6 +137,10 @@ func NewCachedProviderScope(cache *cache.LRUExpireCache, cloud clientconfig.Clou return scope, nil } +func (s *providerScope) NewAddressScopeClient() (clients.AddressScopeClient, error) { + return clients.NewAddressScopeClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) NewComputeClient() (clients.ComputeClient, error) { return clients.NewComputeClient(s.providerClient, s.providerClientOpts) } diff --git a/internal/scope/scope.go b/internal/scope/scope.go index 8baa7f404..20aa25cf5 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -48,6 +48,7 @@ type Factory interface { // Scope contains arguments common to most operations. type Scope interface { + NewAddressScopeClient() (osclients.AddressScopeClient, error) NewComputeClient() (osclients.ComputeClient, error) NewDomainClient() (osclients.DomainClient, error) NewEndpointClient() (osclients.EndpointClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index 10cedb065..f54466d84 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -2,6 +2,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: +- ./internal/controllers/addressscope/tests/ - ./internal/controllers/domain/tests/ - ./internal/controllers/endpoint/tests/ - ./internal/controllers/flavor/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscope.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscope.go new file mode 100644 index 000000000..7b43dfb87 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscope.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" +) + +// AddressScopeApplyConfiguration represents a declarative configuration of the AddressScope type for use +// with apply. +type AddressScopeApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *AddressScopeSpecApplyConfiguration `json:"spec,omitempty"` + Status *AddressScopeStatusApplyConfiguration `json:"status,omitempty"` +} + +// AddressScope constructs a declarative configuration of the AddressScope type for use with +// apply. +func AddressScope(name, namespace string) *AddressScopeApplyConfiguration { + b := &AddressScopeApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("AddressScope") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractAddressScope extracts the applied configuration owned by fieldManager from +// addressScope. If no managedFields are found in addressScope for fieldManager, a +// AddressScopeApplyConfiguration 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. +// addressScope must be a unmodified AddressScope API object that was retrieved from the Kubernetes API. +// ExtractAddressScope 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 ExtractAddressScope(addressScope *apiv1alpha1.AddressScope, fieldManager string) (*AddressScopeApplyConfiguration, error) { + return extractAddressScope(addressScope, fieldManager, "") +} + +// ExtractAddressScopeStatus is the same as ExtractAddressScope except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractAddressScopeStatus(addressScope *apiv1alpha1.AddressScope, fieldManager string) (*AddressScopeApplyConfiguration, error) { + return extractAddressScope(addressScope, fieldManager, "status") +} + +func extractAddressScope(addressScope *apiv1alpha1.AddressScope, fieldManager string, subresource string) (*AddressScopeApplyConfiguration, error) { + b := &AddressScopeApplyConfiguration{} + err := managedfields.ExtractInto(addressScope, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScope"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(addressScope.Name) + b.WithNamespace(addressScope.Namespace) + + b.WithKind("AddressScope") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b AddressScopeApplyConfiguration) 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 *AddressScopeApplyConfiguration) WithKind(value string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithAPIVersion(value string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithName(value string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithGenerateName(value string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithNamespace(value string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithUID(value types.UID) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithResourceVersion(value string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithGeneration(value int64) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithCreationTimestamp(value metav1.Time) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithLabels(entries map[string]string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithAnnotations(entries map[string]string) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithFinalizers(values ...string) *AddressScopeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *AddressScopeApplyConfiguration) 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 *AddressScopeApplyConfiguration) WithSpec(value *AddressScopeSpecApplyConfiguration) *AddressScopeApplyConfiguration { + 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 *AddressScopeApplyConfiguration) WithStatus(value *AddressScopeStatusApplyConfiguration) *AddressScopeApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *AddressScopeApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *AddressScopeApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *AddressScopeApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *AddressScopeApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopefilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopefilter.go new file mode 100644 index 000000000..646451d21 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopefilter.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" +) + +// AddressScopeFilterApplyConfiguration represents a declarative configuration of the AddressScopeFilter type for use +// with apply. +type AddressScopeFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + IPVersion *apiv1alpha1.IPVersion `json:"ipVersion,omitempty"` + Shared *bool `json:"shared,omitempty"` +} + +// AddressScopeFilterApplyConfiguration constructs a declarative configuration of the AddressScopeFilter type for use with +// apply. +func AddressScopeFilter() *AddressScopeFilterApplyConfiguration { + return &AddressScopeFilterApplyConfiguration{} +} + +// 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 *AddressScopeFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *AddressScopeFilterApplyConfiguration { + b.Name = &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 *AddressScopeFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *AddressScopeFilterApplyConfiguration { + b.ProjectRef = &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 *AddressScopeFilterApplyConfiguration) WithIPVersion(value apiv1alpha1.IPVersion) *AddressScopeFilterApplyConfiguration { + 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 *AddressScopeFilterApplyConfiguration) WithShared(value bool) *AddressScopeFilterApplyConfiguration { + b.Shared = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopeimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopeimport.go new file mode 100644 index 000000000..a1e787e7a --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopeimport.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 + +// AddressScopeImportApplyConfiguration represents a declarative configuration of the AddressScopeImport type for use +// with apply. +type AddressScopeImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *AddressScopeFilterApplyConfiguration `json:"filter,omitempty"` +} + +// AddressScopeImportApplyConfiguration constructs a declarative configuration of the AddressScopeImport type for use with +// apply. +func AddressScopeImport() *AddressScopeImportApplyConfiguration { + return &AddressScopeImportApplyConfiguration{} +} + +// 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 *AddressScopeImportApplyConfiguration) WithID(value string) *AddressScopeImportApplyConfiguration { + 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 *AddressScopeImportApplyConfiguration) WithFilter(value *AddressScopeFilterApplyConfiguration) *AddressScopeImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcespec.go new file mode 100644 index 000000000..8fb3db96b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcespec.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" +) + +// AddressScopeResourceSpecApplyConfiguration represents a declarative configuration of the AddressScopeResourceSpec type for use +// with apply. +type AddressScopeResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + IPVersion *apiv1alpha1.IPVersion `json:"ipVersion,omitempty"` + Shared *bool `json:"shared,omitempty"` +} + +// AddressScopeResourceSpecApplyConfiguration constructs a declarative configuration of the AddressScopeResourceSpec type for use with +// apply. +func AddressScopeResourceSpec() *AddressScopeResourceSpecApplyConfiguration { + return &AddressScopeResourceSpecApplyConfiguration{} +} + +// 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 *AddressScopeResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *AddressScopeResourceSpecApplyConfiguration { + b.Name = &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 *AddressScopeResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *AddressScopeResourceSpecApplyConfiguration { + b.ProjectRef = &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 *AddressScopeResourceSpecApplyConfiguration) WithIPVersion(value apiv1alpha1.IPVersion) *AddressScopeResourceSpecApplyConfiguration { + 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 *AddressScopeResourceSpecApplyConfiguration) WithShared(value bool) *AddressScopeResourceSpecApplyConfiguration { + b.Shared = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcestatus.go new file mode 100644 index 000000000..baae40d75 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscoperesourcestatus.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 + +// AddressScopeResourceStatusApplyConfiguration represents a declarative configuration of the AddressScopeResourceStatus type for use +// with apply. +type AddressScopeResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + IPVersion *int32 `json:"ipVersion,omitempty"` + Shared *bool `json:"shared,omitempty"` +} + +// AddressScopeResourceStatusApplyConfiguration constructs a declarative configuration of the AddressScopeResourceStatus type for use with +// apply. +func AddressScopeResourceStatus() *AddressScopeResourceStatusApplyConfiguration { + return &AddressScopeResourceStatusApplyConfiguration{} +} + +// 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 *AddressScopeResourceStatusApplyConfiguration) WithName(value string) *AddressScopeResourceStatusApplyConfiguration { + b.Name = &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 *AddressScopeResourceStatusApplyConfiguration) WithProjectID(value string) *AddressScopeResourceStatusApplyConfiguration { + b.ProjectID = &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 *AddressScopeResourceStatusApplyConfiguration) WithIPVersion(value int32) *AddressScopeResourceStatusApplyConfiguration { + 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 *AddressScopeResourceStatusApplyConfiguration) WithShared(value bool) *AddressScopeResourceStatusApplyConfiguration { + b.Shared = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go new file mode 100644 index 000000000..4a42ce57c --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.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" +) + +// AddressScopeSpecApplyConfiguration represents a declarative configuration of the AddressScopeSpec type for use +// with apply. +type AddressScopeSpecApplyConfiguration struct { + Import *AddressScopeImportApplyConfiguration `json:"import,omitempty"` + Resource *AddressScopeResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// AddressScopeSpecApplyConfiguration constructs a declarative configuration of the AddressScopeSpec type for use with +// apply. +func AddressScopeSpec() *AddressScopeSpecApplyConfiguration { + return &AddressScopeSpecApplyConfiguration{} +} + +// 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 *AddressScopeSpecApplyConfiguration) WithImport(value *AddressScopeImportApplyConfiguration) *AddressScopeSpecApplyConfiguration { + 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 *AddressScopeSpecApplyConfiguration) WithResource(value *AddressScopeResourceSpecApplyConfiguration) *AddressScopeSpecApplyConfiguration { + 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 *AddressScopeSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *AddressScopeSpecApplyConfiguration { + 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 *AddressScopeSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *AddressScopeSpecApplyConfiguration { + 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 *AddressScopeSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *AddressScopeSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go new file mode 100644 index 000000000..c2d823af0 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.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" +) + +// AddressScopeStatusApplyConfiguration represents a declarative configuration of the AddressScopeStatus type for use +// with apply. +type AddressScopeStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *AddressScopeResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// AddressScopeStatusApplyConfiguration constructs a declarative configuration of the AddressScopeStatus type for use with +// apply. +func AddressScopeStatus() *AddressScopeStatusApplyConfiguration { + return &AddressScopeStatusApplyConfiguration{} +} + +// 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 *AddressScopeStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *AddressScopeStatusApplyConfiguration { + 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 *AddressScopeStatusApplyConfiguration) WithID(value string) *AddressScopeStatusApplyConfiguration { + 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 *AddressScopeStatusApplyConfiguration) WithResource(value *AddressScopeResourceStatusApplyConfiguration) *AddressScopeStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 87e4f6e86..bca4e6606 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -48,6 +48,118 @@ var schemaYAML = typed.YAMLObject(`types: - name: subnetRef type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScope + 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.AddressScopeSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeFilter + map: + fields: + - name: ipVersion + type: + scalar: numeric + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: shared + type: + scalar: boolean +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeResourceSpec + map: + fields: + - name: ipVersion + type: + scalar: numeric + default: 0 + - name: name + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: shared + type: + scalar: boolean +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeResourceStatus + map: + fields: + - name: ipVersion + type: + scalar: numeric + - name: name + type: + scalar: string + - name: projectID + type: + scalar: string + - name: shared + type: + scalar: boolean +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeSpec + 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.AddressScopeImport + - 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.AddressScopeResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeStatus + 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.AddressScopeResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AllocationPool map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 1b58223cf..6460b10e6 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -34,6 +34,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { // Group=openstack.k-orc.cloud, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithKind("Address"): return &apiv1alpha1.AddressApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScope"): + return &apiv1alpha1.AddressScopeApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScopeFilter"): + return &apiv1alpha1.AddressScopeFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScopeImport"): + return &apiv1alpha1.AddressScopeImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScopeResourceSpec"): + return &apiv1alpha1.AddressScopeResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScopeResourceStatus"): + return &apiv1alpha1.AddressScopeResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScopeSpec"): + return &apiv1alpha1.AddressScopeSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("AddressScopeStatus"): + return &apiv1alpha1.AddressScopeStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("AllocationPool"): return &apiv1alpha1.AllocationPoolApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("AllocationPoolStatus"): diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/addressscope.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/addressscope.go new file mode 100644 index 000000000..463d3a12c --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/addressscope.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" +) + +// AddressScopesGetter has a method to return a AddressScopeInterface. +// A group's client should implement this interface. +type AddressScopesGetter interface { + AddressScopes(namespace string) AddressScopeInterface +} + +// AddressScopeInterface has methods to work with AddressScope resources. +type AddressScopeInterface interface { + Create(ctx context.Context, addressScope *apiv1alpha1.AddressScope, opts v1.CreateOptions) (*apiv1alpha1.AddressScope, error) + Update(ctx context.Context, addressScope *apiv1alpha1.AddressScope, opts v1.UpdateOptions) (*apiv1alpha1.AddressScope, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, addressScope *apiv1alpha1.AddressScope, opts v1.UpdateOptions) (*apiv1alpha1.AddressScope, 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.AddressScope, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.AddressScopeList, 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.AddressScope, err error) + Apply(ctx context.Context, addressScope *applyconfigurationapiv1alpha1.AddressScopeApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.AddressScope, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, addressScope *applyconfigurationapiv1alpha1.AddressScopeApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.AddressScope, err error) + AddressScopeExpansion +} + +// addressScopes implements AddressScopeInterface +type addressScopes struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.AddressScope, *apiv1alpha1.AddressScopeList, *applyconfigurationapiv1alpha1.AddressScopeApplyConfiguration] +} + +// newAddressScopes returns a AddressScopes +func newAddressScopes(c *OpenstackV1alpha1Client, namespace string) *addressScopes { + return &addressScopes{ + gentype.NewClientWithListAndApply[*apiv1alpha1.AddressScope, *apiv1alpha1.AddressScopeList, *applyconfigurationapiv1alpha1.AddressScopeApplyConfiguration]( + "addressscopes", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.AddressScope { return &apiv1alpha1.AddressScope{} }, + func() *apiv1alpha1.AddressScopeList { return &apiv1alpha1.AddressScopeList{} }, + ), + } +} 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 7c2e4e67d..119869731 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -28,6 +28,7 @@ import ( type OpenstackV1alpha1Interface interface { RESTClient() rest.Interface + AddressScopesGetter DomainsGetter EndpointsGetter FlavorsGetter @@ -56,6 +57,10 @@ type OpenstackV1alpha1Client struct { restClient rest.Interface } +func (c *OpenstackV1alpha1Client) AddressScopes(namespace string) AddressScopeInterface { + return newAddressScopes(c, namespace) +} + func (c *OpenstackV1alpha1Client) Domains(namespace string) DomainInterface { return newDomains(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_addressscope.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_addressscope.go new file mode 100644 index 000000000..549024d55 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_addressscope.go @@ -0,0 +1,53 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeAddressScopes implements AddressScopeInterface +type fakeAddressScopes struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.AddressScope, *v1alpha1.AddressScopeList, *apiv1alpha1.AddressScopeApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeAddressScopes(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.AddressScopeInterface { + return &fakeAddressScopes{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.AddressScope, *v1alpha1.AddressScopeList, *apiv1alpha1.AddressScopeApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("addressscopes"), + v1alpha1.SchemeGroupVersion.WithKind("AddressScope"), + func() *v1alpha1.AddressScope { return &v1alpha1.AddressScope{} }, + func() *v1alpha1.AddressScopeList { return &v1alpha1.AddressScopeList{} }, + func(dst, src *v1alpha1.AddressScopeList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.AddressScopeList) []*v1alpha1.AddressScope { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.AddressScopeList, items []*v1alpha1.AddressScope) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} 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 2b7ba89cc..c9d511137 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 @@ -28,6 +28,10 @@ type FakeOpenstackV1alpha1 struct { *testing.Fake } +func (c *FakeOpenstackV1alpha1) AddressScopes(namespace string) v1alpha1.AddressScopeInterface { + return newFakeAddressScopes(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Domains(namespace string) v1alpha1.DomainInterface { return newFakeDomains(c, namespace) } 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 e34607a4b..090359485 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -18,6 +18,8 @@ limitations under the License. package v1alpha1 +type AddressScopeExpansion interface{} + type DomainExpansion interface{} type EndpointExpansion interface{} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/addressscope.go b/pkg/clients/informers/externalversions/api/v1alpha1/addressscope.go new file mode 100644 index 000000000..39f360e11 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/addressscope.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" +) + +// AddressScopeInformer provides access to a shared informer and lister for +// AddressScopes. +type AddressScopeInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.AddressScopeLister +} + +type addressScopeInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewAddressScopeInformer constructs a new informer for AddressScope 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 NewAddressScopeInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredAddressScopeInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredAddressScopeInformer constructs a new informer for AddressScope 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 NewFilteredAddressScopeInformer(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().AddressScopes(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().AddressScopes(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().AddressScopes(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().AddressScopes(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.AddressScope{}, + resyncPeriod, + indexers, + ) +} + +func (f *addressScopeInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredAddressScopeInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *addressScopeInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.AddressScope{}, f.defaultInformer) +} + +func (f *addressScopeInformer) Lister() apiv1alpha1.AddressScopeLister { + return apiv1alpha1.NewAddressScopeLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index c9f62ae9c..a853d66ab 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -24,6 +24,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // AddressScopes returns a AddressScopeInformer. + AddressScopes() AddressScopeInformer // Domains returns a DomainInformer. Domains() DomainInformer // Endpoints returns a EndpointInformer. @@ -79,6 +81,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// AddressScopes returns a AddressScopeInformer. +func (v *version) AddressScopes() AddressScopeInformer { + return &addressScopeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Domains returns a DomainInformer. func (v *version) Domains() DomainInformer { return &domainInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index a2cd276ae..04db4c8da 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -53,6 +53,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=openstack.k-orc.cloud, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("addressscopes"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().AddressScopes().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("domains"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Domains().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("endpoints"): diff --git a/pkg/clients/listers/api/v1alpha1/addressscope.go b/pkg/clients/listers/api/v1alpha1/addressscope.go new file mode 100644 index 000000000..b2a8b7929 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/addressscope.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" +) + +// AddressScopeLister helps list AddressScopes. +// All objects returned here must be treated as read-only. +type AddressScopeLister interface { + // List lists all AddressScopes in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.AddressScope, err error) + // AddressScopes returns an object that can list and get AddressScopes. + AddressScopes(namespace string) AddressScopeNamespaceLister + AddressScopeListerExpansion +} + +// addressScopeLister implements the AddressScopeLister interface. +type addressScopeLister struct { + listers.ResourceIndexer[*apiv1alpha1.AddressScope] +} + +// NewAddressScopeLister returns a new AddressScopeLister. +func NewAddressScopeLister(indexer cache.Indexer) AddressScopeLister { + return &addressScopeLister{listers.New[*apiv1alpha1.AddressScope](indexer, apiv1alpha1.Resource("addressscope"))} +} + +// AddressScopes returns an object that can list and get AddressScopes. +func (s *addressScopeLister) AddressScopes(namespace string) AddressScopeNamespaceLister { + return addressScopeNamespaceLister{listers.NewNamespaced[*apiv1alpha1.AddressScope](s.ResourceIndexer, namespace)} +} + +// AddressScopeNamespaceLister helps list and get AddressScopes. +// All objects returned here must be treated as read-only. +type AddressScopeNamespaceLister interface { + // List lists all AddressScopes in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.AddressScope, err error) + // Get retrieves the AddressScope from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.AddressScope, error) + AddressScopeNamespaceListerExpansion +} + +// addressScopeNamespaceLister implements the AddressScopeNamespaceLister +// interface. +type addressScopeNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.AddressScope] +} diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index e2fc3b2d2..112762597 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -18,6 +18,14 @@ limitations under the License. package v1alpha1 +// AddressScopeListerExpansion allows custom methods to be added to +// AddressScopeLister. +type AddressScopeListerExpansion interface{} + +// AddressScopeNamespaceListerExpansion allows custom methods to be added to +// AddressScopeNamespaceLister. +type AddressScopeNamespaceListerExpansion interface{} + // DomainListerExpansion allows custom methods to be added to // DomainLister. type DomainListerExpansion interface{} diff --git a/test/apivalidations/addressscope_test.go b/test/apivalidations/addressscope_test.go new file mode 100644 index 000000000..2ac6246d9 --- /dev/null +++ b/test/apivalidations/addressscope_test.go @@ -0,0 +1,77 @@ +/* +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 ( + addressScopeObjName = "addressscope" +) + +func addressScopeStub(namespace *corev1.Namespace) *orcv1alpha1.AddressScope { + obj := &orcv1alpha1.AddressScope{} + obj.Name = addressScopeObjName + obj.Namespace = namespace.Name + return obj +} + +func baseAddressScopePatch(addressScope client.Object) *applyconfigv1alpha1.AddressScopeApplyConfiguration { + return applyconfigv1alpha1.AddressScope(addressScope.GetName(), addressScope.GetNamespace()). + WithSpec(applyconfigv1alpha1.AddressScopeSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +var _ = Describe("ORC AddressScope API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + When("updating the shared field", func() { + It("should permit share a unshared address scope", func(ctx context.Context) { + addressScope := addressScopeStub(namespace) + patch := baseAddressScopePatch(addressScope) + patch.Spec.WithResource(applyconfigv1alpha1.AddressScopeResourceSpec(). + WithIPVersion(orcv1alpha1.IPVersion(4)). + WithShared(false)) + Expect(applyObj(ctx, addressScope, patch)).To(Succeed()) + patch.Spec.WithResource(patch.Spec.Resource).Resource.WithShared(true) + Expect(applyObj(ctx, addressScope, patch)).To(Succeed()) + }) + + It("should not permit unshare a shared address scope", func(ctx context.Context) { + addressScope := addressScopeStub(namespace) + patch := baseAddressScopePatch(addressScope) + patch.Spec.WithResource(applyconfigv1alpha1.AddressScopeResourceSpec(). + WithIPVersion(orcv1alpha1.IPVersion(4)). + WithShared(true)) + Expect(applyObj(ctx, addressScope, patch)).To(Succeed()) + patch.Spec.WithResource(patch.Spec.Resource).Resource.WithShared(false) + Expect(applyObj(ctx, addressScope, patch)).To(MatchError(ContainSubstring("shared address scope can't be unshared"))) + }) + }) +}) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index ebec04a84..963cc5cdb 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -10,6 +10,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API ### Resource Types +- [AddressScope](#addressscope) - [Domain](#domain) - [Endpoint](#endpoint) - [Flavor](#flavor) @@ -51,12 +52,141 @@ _Appears in:_ | `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef references the subnet from which to allocate the IP
address. | | MaxLength: 253
MinLength: 1
| +#### AddressScope +AddressScope is the Schema for an ORC resource. + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `AddressScope` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[AddressScopeSpec](#addressscopespec)_ | spec specifies the desired state of the resource. | | | +| `status` _[AddressScopeStatus](#addressscopestatus)_ | status defines the observed state of the resource. | | | + + +#### AddressScopeFilter + + + +AddressScopeFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [AddressScopeImport](#addressscopeimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. | | Enum: [4 6]
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. | | | + + +#### AddressScopeImport + + + +AddressScopeImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [AddressScopeSpec](#addressscopespec) + +| 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
| +| `filter` _[AddressScopeFilter](#addressscopefilter)_ | 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
| + + +#### AddressScopeResourceSpec + + + +AddressScopeResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [AddressScopeSpec](#addressscopespec) + +| 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: `^[^,]+$`
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. | | Enum: [4 6]
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. We can't unshared a shared address scope; Neutron
enforces this. | | | + + +#### AddressScopeResourceStatus + + + +AddressScopeResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [AddressScopeStatus](#addressscopestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| +| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
| +| `ipVersion` _integer_ | ipVersion is the IP protocol version. | | | +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. | | | + + +#### AddressScopeSpec + + + +AddressScopeSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [AddressScope](#addressscope) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[AddressScopeImport](#addressscopeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[AddressScopeResourceSpec](#addressscoperesourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | + + +#### AddressScopeStatus + + + +AddressScopeStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [AddressScope](#addressscope) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| +| `resource` _[AddressScopeResourceStatus](#addressscoperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + #### AllocationPool @@ -171,6 +301,7 @@ CloudCredentialsReference is a reference to a secret containing OpenStack creden _Appears in:_ +- [AddressScopeSpec](#addressscopespec) - [DomainSpec](#domainspec) - [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) @@ -1103,6 +1234,8 @@ _Validation:_ - Enum: [4 6] _Appears in:_ +- [AddressScopeFilter](#addressscopefilter) +- [AddressScopeResourceSpec](#addressscoperesourcespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) @@ -1851,6 +1984,7 @@ _Appears in:_ _Appears in:_ +- [AddressScopeSpec](#addressscopespec) - [DomainSpec](#domainspec) - [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) @@ -1887,6 +2021,7 @@ _Validation:_ - Enum: [managed unmanaged] _Appears in:_ +- [AddressScopeSpec](#addressscopespec) - [DomainSpec](#domainspec) - [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) From 0ad51e05de104aad3cd452c69f0873276e97d4e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:33:36 +0000 Subject: [PATCH 069/121] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 2 updates Bumps the all-go-mod-patch-and-minor group with 2 updates in the / directory: [github.com/gophercloud/gophercloud/v2](https://github.com/gophercloud/gophercloud) and [golang.org/x/text](https://github.com/golang/text). Updates `github.com/gophercloud/gophercloud/v2` from 2.11.0 to 2.11.1 - [Release notes](https://github.com/gophercloud/gophercloud/releases) - [Changelog](https://github.com/gophercloud/gophercloud/blob/main/CHANGELOG.md) - [Commits](https://github.com/gophercloud/gophercloud/compare/v2.11.0...v2.11.1) Updates `golang.org/x/text` from 0.34.0 to 0.35.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.34.0...v0.35.0) --- updated-dependencies: - dependency-name: github.com/gophercloud/gophercloud/v2 dependency-version: 2.11.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: golang.org/x/text dependency-version: 0.35.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 8a10195dc..99ba5c934 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-logr/logr v1.4.3 github.com/google/go-cmp v0.7.0 - github.com/gophercloud/gophercloud/v2 v2.11.0 + github.com/gophercloud/gophercloud/v2 v2.11.1 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 github.com/onsi/ginkgo/v2 v2.28.1 github.com/onsi/gomega v1.39.1 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 - golang.org/x/text v0.34.0 + golang.org/x/text v0.35.0 k8s.io/api v0.34.5 k8s.io/apimachinery v0.34.5 k8s.io/client-go v0.34.5 @@ -84,14 +84,14 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.19.0 // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.39.0 // indirect + golang.org/x/term v0.40.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index cec4f0417..0655f923b 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/v github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gophercloud/gophercloud/v2 v2.11.0 h1:S0Dp8wPE4mSyv7D0/kWGHnkbuKbzHYm4lQh+FcRRDFM= -github.com/gophercloud/gophercloud/v2 v2.11.0/go.mod h1:fai1ZgWxmROxYcEN3SKY0tQF3Uh0DDCAXU9q/xSQK6I= +github.com/gophercloud/gophercloud/v2 v2.11.1 h1:jCs4vLH8sJgRqrPzqVfWgl7uI6JnIIlsgeIRM0uHjxY= +github.com/gophercloud/gophercloud/v2 v2.11.1/go.mod h1:Rm0YvKQ4QYX2rY9XaDKnjRzSGwlG5ge4h6ABYnmkKQM= github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 h1:LS70kbNdqoalMwLXEzP9Xb/cYv9UCzWioXaOynxrytc= github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1/go.mod h1:qDhuzCRKi90/Yyl/yEqkg8+qABEvK44LhP0D3GWKGtY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= @@ -207,40 +207,40 @@ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= From fe42805cb4c59dfa599955adb545df6d8c28ad46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:22:15 +0100 Subject: [PATCH 070/121] test: add API validation tests for Domain --- test/apivalidations/domain_test.go | 160 +++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 test/apivalidations/domain_test.go diff --git a/test/apivalidations/domain_test.go b/test/apivalidations/domain_test.go new file mode 100644 index 000000000..43d61da72 --- /dev/null +++ b/test/apivalidations/domain_test.go @@ -0,0 +1,160 @@ +/* +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 ( + domainName = "domain" + domainID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func domainStub(namespace *corev1.Namespace) *orcv1alpha1.Domain { + obj := &orcv1alpha1.Domain{} + obj.Name = domainName + obj.Namespace = namespace.Name + return obj +} + +func testDomainResource() *applyconfigv1alpha1.DomainResourceSpecApplyConfiguration { + return applyconfigv1alpha1.DomainResourceSpec() +} + +func baseDomainPatch(domain client.Object) *applyconfigv1alpha1.DomainApplyConfiguration { + return applyconfigv1alpha1.Domain(domain.GetName(), domain.GetNamespace()). + WithSpec(applyconfigv1alpha1.DomainSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testDomainImport() *applyconfigv1alpha1.DomainImportApplyConfiguration { + return applyconfigv1alpha1.DomainImport().WithID(domainID) +} + +var _ = Describe("ORC Domain API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal domain and managementPolicy should default to managed", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec.WithResource(testDomainResource()) + Expect(applyObj(ctx, domain, patch)).To(Succeed()) + Expect(domain.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testDomainImport()) + Expect(applyObj(ctx, domain, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testDomainImport()). + WithResource(testDomainResource()) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.DomainImport()) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.DomainImport(). + WithFilter(applyconfigv1alpha1.DomainFilter())) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.DomainImport(). + WithFilter(applyconfigv1alpha1.DomainFilter().WithName("foo"))) + Expect(applyObj(ctx, domain, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testDomainResource()) + Expect(applyObj(ctx, domain, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec. + WithImport(testDomainImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testDomainResource()) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec. + WithImport(testDomainImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + domain := domainStub(namespace) + patch := baseDomainPatch(domain) + patch.Spec.WithResource(testDomainResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, domain, patch)).To(Succeed()) + Expect(domain.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From c93588489dc911309cf641e0a6c30fd7e0ce1564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:25:26 +0100 Subject: [PATCH 071/121] test: add API validation tests for Role --- test/apivalidations/role_test.go | 172 +++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 test/apivalidations/role_test.go diff --git a/test/apivalidations/role_test.go b/test/apivalidations/role_test.go new file mode 100644 index 000000000..019543acf --- /dev/null +++ b/test/apivalidations/role_test.go @@ -0,0 +1,172 @@ +/* +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 ( + roleName = "role" + roleID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae121" +) + +func roleStub(namespace *corev1.Namespace) *orcv1alpha1.Role { + obj := &orcv1alpha1.Role{} + obj.Name = roleName + obj.Namespace = namespace.Name + return obj +} + +func testRoleResource() *applyconfigv1alpha1.RoleResourceSpecApplyConfiguration { + return applyconfigv1alpha1.RoleResourceSpec() +} + +func baseRolePatch(role client.Object) *applyconfigv1alpha1.RoleApplyConfiguration { + return applyconfigv1alpha1.Role(role.GetName(), role.GetNamespace()). + WithSpec(applyconfigv1alpha1.RoleSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testRoleImport() *applyconfigv1alpha1.RoleImportApplyConfiguration { + return applyconfigv1alpha1.RoleImport().WithID(roleID) +} + +var _ = Describe("ORC Role API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal role and managementPolicy should default to managed", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec.WithResource(testRoleResource()) + Expect(applyObj(ctx, role, patch)).To(Succeed()) + Expect(role.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testRoleImport()) + Expect(applyObj(ctx, role, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testRoleImport()). + WithResource(testRoleResource()) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.RoleImport()) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.RoleImport(). + WithFilter(applyconfigv1alpha1.RoleFilter())) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.RoleImport(). + WithFilter(applyconfigv1alpha1.RoleFilter().WithName("foo"))) + Expect(applyObj(ctx, role, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testRoleResource()) + Expect(applyObj(ctx, role, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec. + WithImport(testRoleImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testRoleResource()) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec. + WithImport(testRoleImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec.WithResource(testRoleResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, role, patch)).To(Succeed()) + Expect(role.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) + + It("should have immutable domainRef", func(ctx context.Context) { + role := roleStub(namespace) + patch := baseRolePatch(role) + patch.Spec.WithResource(applyconfigv1alpha1.RoleResourceSpec(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, role, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.RoleResourceSpec(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) +}) From c8db0bee892852cbecc029db4a510d502a831966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:25:29 +0100 Subject: [PATCH 072/121] test: add API validation tests for Group --- test/apivalidations/group_test.go | 172 ++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 test/apivalidations/group_test.go diff --git a/test/apivalidations/group_test.go b/test/apivalidations/group_test.go new file mode 100644 index 000000000..1e582f43e --- /dev/null +++ b/test/apivalidations/group_test.go @@ -0,0 +1,172 @@ +/* +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 ( + groupName = "group" + groupID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae122" +) + +func groupStub(namespace *corev1.Namespace) *orcv1alpha1.Group { + obj := &orcv1alpha1.Group{} + obj.Name = groupName + obj.Namespace = namespace.Name + return obj +} + +func testGroupResource() *applyconfigv1alpha1.GroupResourceSpecApplyConfiguration { + return applyconfigv1alpha1.GroupResourceSpec() +} + +func baseGroupPatch(group client.Object) *applyconfigv1alpha1.GroupApplyConfiguration { + return applyconfigv1alpha1.Group(group.GetName(), group.GetNamespace()). + WithSpec(applyconfigv1alpha1.GroupSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testGroupImport() *applyconfigv1alpha1.GroupImportApplyConfiguration { + return applyconfigv1alpha1.GroupImport().WithID(groupID) +} + +var _ = Describe("ORC Group API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal group and managementPolicy should default to managed", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec.WithResource(testGroupResource()) + Expect(applyObj(ctx, group, patch)).To(Succeed()) + Expect(group.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testGroupImport()) + Expect(applyObj(ctx, group, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testGroupImport()). + WithResource(testGroupResource()) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.GroupImport()) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.GroupImport(). + WithFilter(applyconfigv1alpha1.GroupFilter())) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.GroupImport(). + WithFilter(applyconfigv1alpha1.GroupFilter().WithName("foo"))) + Expect(applyObj(ctx, group, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testGroupResource()) + Expect(applyObj(ctx, group, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec. + WithImport(testGroupImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testGroupResource()) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec. + WithImport(testGroupImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec.WithResource(testGroupResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, group, patch)).To(Succeed()) + Expect(group.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) + + It("should have immutable domainRef", func(ctx context.Context) { + group := groupStub(namespace) + patch := baseGroupPatch(group) + patch.Spec.WithResource(applyconfigv1alpha1.GroupResourceSpec(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, group, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.GroupResourceSpec(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) +}) From fc562ef5ac4e506c4b6f4fbb2c6a0fc4a384d804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:26:38 +0100 Subject: [PATCH 073/121] test: add API validation tests for Service --- test/apivalidations/service_test.go | 167 ++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 test/apivalidations/service_test.go diff --git a/test/apivalidations/service_test.go b/test/apivalidations/service_test.go new file mode 100644 index 000000000..82f57f02d --- /dev/null +++ b/test/apivalidations/service_test.go @@ -0,0 +1,167 @@ +/* +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 ( + serviceName = "service" + serviceID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae123" +) + +func serviceStub(namespace *corev1.Namespace) *orcv1alpha1.Service { + obj := &orcv1alpha1.Service{} + obj.Name = serviceName + obj.Namespace = namespace.Name + return obj +} + +func testServiceResource() *applyconfigv1alpha1.ServiceResourceSpecApplyConfiguration { + return applyconfigv1alpha1.ServiceResourceSpec().WithType("compute") +} + +func baseServicePatch(service client.Object) *applyconfigv1alpha1.ServiceApplyConfiguration { + return applyconfigv1alpha1.Service(service.GetName(), service.GetNamespace()). + WithSpec(applyconfigv1alpha1.ServiceSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testServiceImport() *applyconfigv1alpha1.ServiceImportApplyConfiguration { + return applyconfigv1alpha1.ServiceImport().WithID(serviceID) +} + +var _ = Describe("ORC Service API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal service and managementPolicy should default to managed", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec.WithResource(testServiceResource()) + Expect(applyObj(ctx, service, patch)).To(Succeed()) + Expect(service.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject a service without required field type", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec.WithResource(applyconfigv1alpha1.ServiceResourceSpec()) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("spec.resource.type"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testServiceImport()) + Expect(applyObj(ctx, service, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testServiceImport()). + WithResource(testServiceResource()) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServiceImport()) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServiceImport(). + WithFilter(applyconfigv1alpha1.ServiceFilter())) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServiceImport(). + WithFilter(applyconfigv1alpha1.ServiceFilter().WithName("foo"))) + Expect(applyObj(ctx, service, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testServiceResource()) + Expect(applyObj(ctx, service, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec. + WithImport(testServiceImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testServiceResource()) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec. + WithImport(testServiceImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + service := serviceStub(namespace) + patch := baseServicePatch(service) + patch.Spec.WithResource(testServiceResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, service, patch)).To(Succeed()) + Expect(service.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 9c9101af306fd503babbce7f32530f8a4922a800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:26:41 +0100 Subject: [PATCH 074/121] test: add API validation tests for KeyPair --- test/apivalidations/keypair_test.go | 194 ++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 test/apivalidations/keypair_test.go diff --git a/test/apivalidations/keypair_test.go b/test/apivalidations/keypair_test.go new file mode 100644 index 000000000..d5b57586b --- /dev/null +++ b/test/apivalidations/keypair_test.go @@ -0,0 +1,194 @@ +/* +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" + "strings" + + . "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 ( + keypairName = "keypair" + keypairID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae124" +) + +func keypairStub(namespace *corev1.Namespace) *orcv1alpha1.KeyPair { + obj := &orcv1alpha1.KeyPair{} + obj.Name = keypairName + obj.Namespace = namespace.Name + return obj +} + +func testKeypairResource() *applyconfigv1alpha1.KeyPairResourceSpecApplyConfiguration { + return applyconfigv1alpha1.KeyPairResourceSpec().WithPublicKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ") +} + +func baseKeypairPatch(keypair client.Object) *applyconfigv1alpha1.KeyPairApplyConfiguration { + return applyconfigv1alpha1.KeyPair(keypair.GetName(), keypair.GetNamespace()). + WithSpec(applyconfigv1alpha1.KeyPairSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testKeypairImport() *applyconfigv1alpha1.KeyPairImportApplyConfiguration { + return applyconfigv1alpha1.KeyPairImport().WithID(keypairID) +} + +var _ = Describe("ORC KeyPair API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal keypair and managementPolicy should default to managed", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithResource(testKeypairResource()) + Expect(applyObj(ctx, keypair, patch)).To(Succeed()) + Expect(keypair.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject a keypair without required field publicKey", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithResource(applyconfigv1alpha1.KeyPairResourceSpec()) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.resource.publicKey"))) + }) + + It("should reject invalid type enum value", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithResource(applyconfigv1alpha1.KeyPairResourceSpec(). + WithPublicKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ"). + WithType("invalid")) + Expect(applyObj(ctx, keypair, patch)).NotTo(Succeed()) + }) + + It("should permit valid type enum values", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithResource(applyconfigv1alpha1.KeyPairResourceSpec(). + WithPublicKey("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ"). + WithType("ssh")) + Expect(applyObj(ctx, keypair, patch)).To(Succeed()) + }) + + It("should reject publicKey exceeding max length", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithResource(applyconfigv1alpha1.KeyPairResourceSpec(). + WithPublicKey(strings.Repeat("a", 16385))) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.resource.publicKey"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testKeypairImport()) + Expect(applyObj(ctx, keypair, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testKeypairImport()). + WithResource(testKeypairResource()) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.KeyPairImport()) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.KeyPairImport(). + WithFilter(applyconfigv1alpha1.KeyPairFilter())) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.KeyPairImport(). + WithFilter(applyconfigv1alpha1.KeyPairFilter().WithName("foo"))) + Expect(applyObj(ctx, keypair, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testKeypairResource()) + Expect(applyObj(ctx, keypair, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec. + WithImport(testKeypairImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testKeypairResource()) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec. + WithImport(testKeypairImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + keypair := keypairStub(namespace) + patch := baseKeypairPatch(keypair) + patch.Spec.WithResource(testKeypairResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, keypair, patch)).To(Succeed()) + Expect(keypair.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 2efd6b889ce1b52775529db672aecb225b36fcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:28:05 +0100 Subject: [PATCH 075/121] test: add API validation tests for Project --- test/apivalidations/project_test.go | 176 ++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 test/apivalidations/project_test.go diff --git a/test/apivalidations/project_test.go b/test/apivalidations/project_test.go new file mode 100644 index 000000000..23f76b84a --- /dev/null +++ b/test/apivalidations/project_test.go @@ -0,0 +1,176 @@ +/* +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 ( + projectObjName = "project" + projectID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae125" +) + +func projectStub(namespace *corev1.Namespace) *orcv1alpha1.Project { + obj := &orcv1alpha1.Project{} + obj.Name = projectObjName + obj.Namespace = namespace.Name + return obj +} + +func testProjectResource() *applyconfigv1alpha1.ProjectResourceSpecApplyConfiguration { + return applyconfigv1alpha1.ProjectResourceSpec() +} + +func baseProjectPatch(project client.Object) *applyconfigv1alpha1.ProjectApplyConfiguration { + return applyconfigv1alpha1.Project(project.GetName(), project.GetNamespace()). + WithSpec(applyconfigv1alpha1.ProjectSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testProjectImport() *applyconfigv1alpha1.ProjectImportApplyConfiguration { + return applyconfigv1alpha1.ProjectImport().WithID(projectID) +} + +var _ = Describe("ORC Project API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal project and managementPolicy should default to managed", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithResource(testProjectResource()) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + Expect(project.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject duplicate tags", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithResource(applyconfigv1alpha1.ProjectResourceSpec(). + WithTags("foo", "bar", "foo")) + Expect(applyObj(ctx, project, patch)).NotTo(Succeed()) + }) + + It("should permit unique tags", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithResource(applyconfigv1alpha1.ProjectResourceSpec(). + WithTags("foo", "bar")) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testProjectImport()) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testProjectImport()). + WithResource(testProjectResource()) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ProjectImport()) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ProjectImport(). + WithFilter(applyconfigv1alpha1.ProjectFilter())) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ProjectImport(). + WithFilter(applyconfigv1alpha1.ProjectFilter().WithName("foo"))) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testProjectResource()) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec. + WithImport(testProjectImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testProjectResource()) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec. + WithImport(testProjectImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithResource(testProjectResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + Expect(project.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 081715b8bba15db9d67c4cd564ac10bb92f963b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:28:08 +0100 Subject: [PATCH 076/121] test: add API validation tests for VolumeType --- test/apivalidations/volumetype_test.go | 169 +++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 test/apivalidations/volumetype_test.go diff --git a/test/apivalidations/volumetype_test.go b/test/apivalidations/volumetype_test.go new file mode 100644 index 000000000..0ce3d227b --- /dev/null +++ b/test/apivalidations/volumetype_test.go @@ -0,0 +1,169 @@ +/* +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 ( + volumeTypeName = "volumetype" + volumeTypeID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae126" +) + +func volumeTypeStub(namespace *corev1.Namespace) *orcv1alpha1.VolumeType { + obj := &orcv1alpha1.VolumeType{} + obj.Name = volumeTypeName + obj.Namespace = namespace.Name + return obj +} + +func testVolumeTypeResource() *applyconfigv1alpha1.VolumeTypeResourceSpecApplyConfiguration { + return applyconfigv1alpha1.VolumeTypeResourceSpec() +} + +func baseVolumeTypePatch(volumeType client.Object) *applyconfigv1alpha1.VolumeTypeApplyConfiguration { + return applyconfigv1alpha1.VolumeType(volumeType.GetName(), volumeType.GetNamespace()). + WithSpec(applyconfigv1alpha1.VolumeTypeSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testVolumeTypeImport() *applyconfigv1alpha1.VolumeTypeImportApplyConfiguration { + return applyconfigv1alpha1.VolumeTypeImport().WithID(volumeTypeID) +} + +var _ = Describe("ORC VolumeType API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal volumetype and managementPolicy should default to managed", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec.WithResource(testVolumeTypeResource()) + Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) + Expect(volumeType.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should permit extraSpecs with required fields", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeTypeResourceSpec(). + WithExtraSpecs(applyconfigv1alpha1.VolumeTypeExtraSpec(). + WithName("key").WithValue("value"))) + Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testVolumeTypeImport()) + Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testVolumeTypeImport()). + WithResource(testVolumeTypeResource()) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.VolumeTypeImport()) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.VolumeTypeImport(). + WithFilter(applyconfigv1alpha1.VolumeTypeFilter())) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.VolumeTypeImport(). + WithFilter(applyconfigv1alpha1.VolumeTypeFilter().WithName("foo"))) + Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testVolumeTypeResource()) + Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec. + WithImport(testVolumeTypeImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testVolumeTypeResource()) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec. + WithImport(testVolumeTypeImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + volumeType := volumeTypeStub(namespace) + patch := baseVolumeTypePatch(volumeType) + patch.Spec.WithResource(testVolumeTypeResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) + Expect(volumeType.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 3b8be2ddd062e15137ee0b9d48030bf9cfad6650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:29:23 +0100 Subject: [PATCH 077/121] test: add API validation tests for User --- test/apivalidations/user_test.go | 184 +++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 test/apivalidations/user_test.go diff --git a/test/apivalidations/user_test.go b/test/apivalidations/user_test.go new file mode 100644 index 000000000..91b4b51e6 --- /dev/null +++ b/test/apivalidations/user_test.go @@ -0,0 +1,184 @@ +/* +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 ( + userName = "user" + userID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae127" +) + +func userStub(namespace *corev1.Namespace) *orcv1alpha1.User { + obj := &orcv1alpha1.User{} + obj.Name = userName + obj.Namespace = namespace.Name + return obj +} + +func testUserResource() *applyconfigv1alpha1.UserResourceSpecApplyConfiguration { + return applyconfigv1alpha1.UserResourceSpec() +} + +func baseUserPatch(user client.Object) *applyconfigv1alpha1.UserApplyConfiguration { + return applyconfigv1alpha1.User(user.GetName(), user.GetNamespace()). + WithSpec(applyconfigv1alpha1.UserSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testUserImport() *applyconfigv1alpha1.UserImportApplyConfiguration { + return applyconfigv1alpha1.UserImport().WithID(userID) +} + +var _ = Describe("ORC User API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal user and managementPolicy should default to managed", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(testUserResource()) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + Expect(user.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should have immutable domainRef", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) + + It("should have immutable defaultProjectRef", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithDefaultProjectRef("project-a")) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithDefaultProjectRef("project-b")) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("defaultProjectRef is immutable"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testUserImport()) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testUserImport()). + WithResource(testUserResource()) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.UserImport()) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.UserImport(). + WithFilter(applyconfigv1alpha1.UserFilter())) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.UserImport(). + WithFilter(applyconfigv1alpha1.UserFilter().WithName("foo"))) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testUserResource()) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec. + WithImport(testUserImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testUserResource()) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec. + WithImport(testUserImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(testUserResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + Expect(user.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 511bb0203736141a0eccaa1b7345b4bdf589d94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:29:23 +0100 Subject: [PATCH 078/121] test: add API validation tests for Endpoint --- test/apivalidations/endpoint_test.go | 241 +++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 test/apivalidations/endpoint_test.go diff --git a/test/apivalidations/endpoint_test.go b/test/apivalidations/endpoint_test.go new file mode 100644 index 000000000..666324996 --- /dev/null +++ b/test/apivalidations/endpoint_test.go @@ -0,0 +1,241 @@ +/* +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 ( + endpointName = "endpoint" + endpointID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae128" +) + +func endpointStub(namespace *corev1.Namespace) *orcv1alpha1.Endpoint { + obj := &orcv1alpha1.Endpoint{} + obj.Name = endpointName + obj.Namespace = namespace.Name + return obj +} + +func testEndpointResource() *applyconfigv1alpha1.EndpointResourceSpecApplyConfiguration { + return applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public"). + WithURL("https://example.com"). + WithServiceRef("my-service") +} + +func baseEndpointPatch(endpoint client.Object) *applyconfigv1alpha1.EndpointApplyConfiguration { + return applyconfigv1alpha1.Endpoint(endpoint.GetName(), endpoint.GetNamespace()). + WithSpec(applyconfigv1alpha1.EndpointSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testEndpointImport() *applyconfigv1alpha1.EndpointImportApplyConfiguration { + return applyconfigv1alpha1.EndpointImport().WithID(endpointID) +} + +var _ = Describe("ORC Endpoint API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal endpoint and managementPolicy should default to managed", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(testEndpointResource()) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + Expect(endpoint.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject an endpoint without required fields", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec()) + Expect(applyObj(ctx, endpoint, patch)).NotTo(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public").WithServiceRef("my-service")) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.resource.url"))) + + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithURL("https://example.com").WithServiceRef("my-service")) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.resource.interface"))) + + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public").WithURL("https://example.com")) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.resource.serviceRef"))) + }) + + It("should reject invalid interface enum value", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("invalid"). + WithURL("https://example.com"). + WithServiceRef("my-service")) + Expect(applyObj(ctx, endpoint, patch)).NotTo(Succeed()) + }) + + DescribeTable("should permit valid interface enum values", + func(ctx context.Context, iface string) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface(iface). + WithURL("https://example.com"). + WithServiceRef("my-service")) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + }, + Entry("admin", "admin"), + Entry("internal", "internal"), + Entry("public", "public"), + ) + + It("should have immutable serviceRef", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public"). + WithURL("https://example.com"). + WithServiceRef("service-a")) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public"). + WithURL("https://example.com"). + WithServiceRef("service-b")) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("serviceRef is immutable"))) + }) + + It("should have immutable description", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public"). + WithURL("https://example.com"). + WithServiceRef("my-service"). + WithDescription("desc-a")) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.EndpointResourceSpec(). + WithInterface("public"). + WithURL("https://example.com"). + WithServiceRef("my-service"). + WithDescription("desc-b")) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("description is immutable"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testEndpointImport()) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testEndpointImport()). + WithResource(testEndpointResource()) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.EndpointImport()) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.EndpointImport(). + WithFilter(applyconfigv1alpha1.EndpointFilter())) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with interface", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.EndpointImport(). + WithFilter(applyconfigv1alpha1.EndpointFilter().WithInterface("public"))) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testEndpointResource()) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec. + WithImport(testEndpointImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testEndpointResource()) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec. + WithImport(testEndpointImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + endpoint := endpointStub(namespace) + patch := baseEndpointPatch(endpoint) + patch.Spec.WithResource(testEndpointResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) + Expect(endpoint.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 95909bfef5f7d451f944595b82a3e573afa41004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:30:43 +0100 Subject: [PATCH 079/121] test: add API validation tests for ServerGroup --- test/apivalidations/servergroup_test.go | 220 ++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 test/apivalidations/servergroup_test.go diff --git a/test/apivalidations/servergroup_test.go b/test/apivalidations/servergroup_test.go new file mode 100644 index 000000000..f211749d9 --- /dev/null +++ b/test/apivalidations/servergroup_test.go @@ -0,0 +1,220 @@ +/* +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 ( + serverGroupName = "servergroup" + serverGroupID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae129" +) + +func serverGroupStub(namespace *corev1.Namespace) *orcv1alpha1.ServerGroup { + obj := &orcv1alpha1.ServerGroup{} + obj.Name = serverGroupName + obj.Namespace = namespace.Name + return obj +} + +func testServerGroupResource() *applyconfigv1alpha1.ServerGroupResourceSpecApplyConfiguration { + return applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy(orcv1alpha1.ServerGroupPolicyAffinity) +} + +func baseServerGroupPatch(serverGroup client.Object) *applyconfigv1alpha1.ServerGroupApplyConfiguration { + return applyconfigv1alpha1.ServerGroup(serverGroup.GetName(), serverGroup.GetNamespace()). + WithSpec(applyconfigv1alpha1.ServerGroupSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testServerGroupImport() *applyconfigv1alpha1.ServerGroupImportApplyConfiguration { + return applyconfigv1alpha1.ServerGroupImport().WithID(serverGroupID) +} + +var _ = Describe("ORC ServerGroup API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal servergroup and managementPolicy should default to managed", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(testServerGroupResource()) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + Expect(serverGroup.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject a servergroup without required field policy", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec()) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("spec.resource.policy"))) + }) + + It("should be immutable", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy(orcv1alpha1.ServerGroupPolicyAffinity)) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy(orcv1alpha1.ServerGroupPolicyAntiAffinity)) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("ServerGroupResourceSpec is immutable"))) + }) + + It("should reject invalid policy enum value", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy("invalid")) + Expect(applyObj(ctx, serverGroup, patch)).NotTo(Succeed()) + }) + + DescribeTable("should permit valid policy enum values", + func(ctx context.Context, policy orcv1alpha1.ServerGroupPolicy) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy(policy)) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + }, + Entry(string(orcv1alpha1.ServerGroupPolicyAffinity), orcv1alpha1.ServerGroupPolicyAffinity), + Entry(string(orcv1alpha1.ServerGroupPolicyAntiAffinity), orcv1alpha1.ServerGroupPolicyAntiAffinity), + Entry(string(orcv1alpha1.ServerGroupPolicySoftAffinity), orcv1alpha1.ServerGroupPolicySoftAffinity), + Entry(string(orcv1alpha1.ServerGroupPolicySoftAntiAffinity), orcv1alpha1.ServerGroupPolicySoftAntiAffinity), + ) + + It("should permit maxServerPerHost with anti-affinity policy", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy(orcv1alpha1.ServerGroupPolicyAntiAffinity). + WithRules(applyconfigv1alpha1.ServerGroupRules().WithMaxServerPerHost(2))) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + }) + + It("should reject maxServerPerHost with non-anti-affinity policy", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(applyconfigv1alpha1.ServerGroupResourceSpec(). + WithPolicy(orcv1alpha1.ServerGroupPolicyAffinity). + WithRules(applyconfigv1alpha1.ServerGroupRules().WithMaxServerPerHost(2))) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("maxServerPerHost can only be used with the anti-affinity policy"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testServerGroupImport()) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testServerGroupImport()). + WithResource(testServerGroupResource()) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServerGroupImport()) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServerGroupImport(). + WithFilter(applyconfigv1alpha1.ServerGroupFilter())) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServerGroupImport(). + WithFilter(applyconfigv1alpha1.ServerGroupFilter().WithName("foo"))) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testServerGroupResource()) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec. + WithImport(testServerGroupImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testServerGroupResource()) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec. + WithImport(testServerGroupImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + serverGroup := serverGroupStub(namespace) + patch := baseServerGroupPatch(serverGroup) + patch.Spec.WithResource(testServerGroupResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) + Expect(serverGroup.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 97289b0bbb3e8e15cfe5efd2bea301e366011056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:30:43 +0100 Subject: [PATCH 080/121] test: add API validation tests for RouterInterface --- test/apivalidations/routerinterface_test.go | 114 ++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/apivalidations/routerinterface_test.go diff --git a/test/apivalidations/routerinterface_test.go b/test/apivalidations/routerinterface_test.go new file mode 100644 index 000000000..85ad56c13 --- /dev/null +++ b/test/apivalidations/routerinterface_test.go @@ -0,0 +1,114 @@ +/* +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 ( + routerInterfaceName = "routerinterface" +) + +func routerInterfaceStub(namespace *corev1.Namespace) *orcv1alpha1.RouterInterface { + obj := &orcv1alpha1.RouterInterface{} + obj.Name = routerInterfaceName + obj.Namespace = namespace.Name + return obj +} + +func baseRouterInterfacePatch(ri client.Object) *applyconfigv1alpha1.RouterInterfaceApplyConfiguration { + return applyconfigv1alpha1.RouterInterface(ri.GetName(), ri.GetNamespace()) +} + +var _ = Describe("ORC RouterInterface API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a valid router interface", func(ctx context.Context) { + ri := routerInterfaceStub(namespace) + patch := baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithRouterRef("my-router"). + WithSubnetRef("my-subnet")) + Expect(applyObj(ctx, ri, patch)).To(Succeed()) + }) + + It("should reject missing required field type", func(ctx context.Context) { + ri := routerInterfaceStub(namespace) + patch := baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithRouterRef("my-router"). + WithSubnetRef("my-subnet")) + Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("spec.type"))) + }) + + It("should reject missing required field routerRef", func(ctx context.Context) { + ri := routerInterfaceStub(namespace) + patch := baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithSubnetRef("my-subnet")) + Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("spec.routerRef"))) + }) + + It("should reject invalid type enum value", func(ctx context.Context) { + ri := routerInterfaceStub(namespace) + patch := baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType("Invalid"). + WithRouterRef("my-router"). + WithSubnetRef("my-subnet")) + Expect(applyObj(ctx, ri, patch)).NotTo(Succeed()) + }) + + It("should require subnetRef when type is Subnet", func(ctx context.Context) { + ri := routerInterfaceStub(namespace) + patch := baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithRouterRef("my-router")) + Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("subnetRef is required when type is 'Subnet'"))) + }) + + It("should be immutable", func(ctx context.Context) { + ri := routerInterfaceStub(namespace) + patch := baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithRouterRef("router-a"). + WithSubnetRef("subnet-a")) + Expect(applyObj(ctx, ri, patch)).To(Succeed()) + + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithRouterRef("router-b"). + WithSubnetRef("subnet-a")) + Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("RouterInterfaceResourceSpec is immutable"))) + }) +}) From 97dac969043ff6db62518428f75af8180b2995f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:32:06 +0100 Subject: [PATCH 081/121] test: add API validation tests for FloatingIP --- test/apivalidations/floatingip_test.go | 233 +++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 test/apivalidations/floatingip_test.go diff --git a/test/apivalidations/floatingip_test.go b/test/apivalidations/floatingip_test.go new file mode 100644 index 000000000..3c9b20f25 --- /dev/null +++ b/test/apivalidations/floatingip_test.go @@ -0,0 +1,233 @@ +/* +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 ( + floatingIPName = "floatingip" + floatingIPID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae130" +) + +func floatingIPStub(namespace *corev1.Namespace) *orcv1alpha1.FloatingIP { + obj := &orcv1alpha1.FloatingIP{} + obj.Name = floatingIPName + obj.Namespace = namespace.Name + return obj +} + +func testFloatingIPResource() *applyconfigv1alpha1.FloatingIPResourceSpecApplyConfiguration { + return applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("my-network") +} + +func baseFloatingIPPatch(fip client.Object) *applyconfigv1alpha1.FloatingIPApplyConfiguration { + return applyconfigv1alpha1.FloatingIP(fip.GetName(), fip.GetNamespace()). + WithSpec(applyconfigv1alpha1.FloatingIPSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testFloatingIPImport() *applyconfigv1alpha1.FloatingIPImportApplyConfiguration { + return applyconfigv1alpha1.FloatingIPImport().WithID(floatingIPID) +} + +var _ = Describe("ORC FloatingIP API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal floatingip and managementPolicy should default to managed", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithResource(testFloatingIPResource()) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + Expect(fip.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should require exactly one of floatingNetworkRef or floatingSubnetRef", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + + // Neither set + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec()) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("Exactly one of 'floatingNetworkRef' or 'floatingSubnetRef' must be set"))) + + // Both set + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("net-a"). + WithFloatingSubnetRef("subnet-a")) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("Exactly one of 'floatingNetworkRef' or 'floatingSubnetRef' must be set"))) + + // Only floatingSubnetRef set - should succeed + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingSubnetRef("subnet-a")) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + }) + + It("should have immutable floatingNetworkRef", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("net-a")) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("net-b")) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("floatingNetworkRef is immutable"))) + }) + + It("should have immutable floatingSubnetRef", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingSubnetRef("subnet-a")) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingSubnetRef("subnet-b")) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("floatingSubnetRef is immutable"))) + }) + + It("should have immutable portRef", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("my-network"). + WithPortRef("port-a")) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("my-network"). + WithPortRef("port-b")) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("portRef is immutable"))) + }) + + It("should have immutable projectRef", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("my-network"). + WithProjectRef("project-a")) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.FloatingIPResourceSpec(). + WithFloatingNetworkRef("my-network"). + WithProjectRef("project-b")) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testFloatingIPImport()) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testFloatingIPImport()). + WithResource(testFloatingIPResource()) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.FloatingIPImport()) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.FloatingIPImport(). + WithFilter(applyconfigv1alpha1.FloatingIPFilter())) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with floatingNetworkRef", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.FloatingIPImport(). + WithFilter(applyconfigv1alpha1.FloatingIPFilter().WithFloatingNetworkRef("my-network"))) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testFloatingIPResource()) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec. + WithImport(testFloatingIPImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testFloatingIPResource()) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec. + WithImport(testFloatingIPImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + fip := floatingIPStub(namespace) + patch := baseFloatingIPPatch(fip) + patch.Spec.WithResource(testFloatingIPResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, fip, patch)).To(Succeed()) + Expect(fip.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 7590c3e344728ac0ad016408ad937f39b54035ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:32:07 +0100 Subject: [PATCH 082/121] test: add API validation tests for Volume --- test/apivalidations/volume_test.go | 220 +++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 test/apivalidations/volume_test.go diff --git a/test/apivalidations/volume_test.go b/test/apivalidations/volume_test.go new file mode 100644 index 000000000..8c7c822e6 --- /dev/null +++ b/test/apivalidations/volume_test.go @@ -0,0 +1,220 @@ +/* +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 ( + volumeName = "volume" + volumeID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae131" +) + +func volumeStub(namespace *corev1.Namespace) *orcv1alpha1.Volume { + obj := &orcv1alpha1.Volume{} + obj.Name = volumeName + obj.Namespace = namespace.Name + return obj +} + +func testVolumeResource() *applyconfigv1alpha1.VolumeResourceSpecApplyConfiguration { + return applyconfigv1alpha1.VolumeResourceSpec().WithSize(1) +} + +func baseVolumePatch(volume client.Object) *applyconfigv1alpha1.VolumeApplyConfiguration { + return applyconfigv1alpha1.Volume(volume.GetName(), volume.GetNamespace()). + WithSpec(applyconfigv1alpha1.VolumeSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testVolumeImport() *applyconfigv1alpha1.VolumeImportApplyConfiguration { + return applyconfigv1alpha1.VolumeImport().WithID(volumeID) +} + +var _ = Describe("ORC Volume API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal volume and managementPolicy should default to managed", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(testVolumeResource()) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + Expect(volume.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject a volume without required field size", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec()) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("spec.resource.size"))) + }) + + It("should reject size less than minimum", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec().WithSize(0)) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("spec.resource.size in body should be greater than or equal to 1"))) + }) + + It("should have immutable size", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec().WithSize(1)) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec().WithSize(2)) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("size is immutable"))) + }) + + It("should have immutable volumeTypeRef", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec(). + WithSize(1).WithVolumeTypeRef("type-a")) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec(). + WithSize(1).WithVolumeTypeRef("type-b")) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("volumeTypeRef is immutable"))) + }) + + It("should have immutable availabilityZone", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec(). + WithSize(1).WithAvailabilityZone("az-a")) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec(). + WithSize(1).WithAvailabilityZone("az-b")) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("availabilityZone is immutable"))) + }) + + It("should have immutable imageRef", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec(). + WithSize(1).WithImageRef("image-a")) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.VolumeResourceSpec(). + WithSize(1).WithImageRef("image-b")) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("imageRef is immutable"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testVolumeImport()) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testVolumeImport()). + WithResource(testVolumeResource()) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.VolumeImport()) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.VolumeImport(). + WithFilter(applyconfigv1alpha1.VolumeFilter())) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.VolumeImport(). + WithFilter(applyconfigv1alpha1.VolumeFilter().WithName("foo"))) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testVolumeResource()) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec. + WithImport(testVolumeImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testVolumeResource()) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec. + WithImport(testVolumeImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + volume := volumeStub(namespace) + patch := baseVolumePatch(volume) + patch.Spec.WithResource(testVolumeResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, volume, patch)).To(Succeed()) + Expect(volume.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 446b81132d5c7e70390c06d2a4b5120135237fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:34:23 +0100 Subject: [PATCH 083/121] test: add API validation tests for Router --- test/apivalidations/router_test.go | 204 +++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 test/apivalidations/router_test.go diff --git a/test/apivalidations/router_test.go b/test/apivalidations/router_test.go new file mode 100644 index 000000000..57e454236 --- /dev/null +++ b/test/apivalidations/router_test.go @@ -0,0 +1,204 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +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 ( + routerObjName = "router" + routerID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae132" +) + +func routerStub(namespace *corev1.Namespace) *orcv1alpha1.Router { + obj := &orcv1alpha1.Router{} + obj.Name = routerObjName + obj.Namespace = namespace.Name + return obj +} + +func testRouterResource() *applyconfigv1alpha1.RouterResourceSpecApplyConfiguration { + return applyconfigv1alpha1.RouterResourceSpec() +} + +func baseRouterPatch(router client.Object) *applyconfigv1alpha1.RouterApplyConfiguration { + return applyconfigv1alpha1.Router(router.GetName(), router.GetNamespace()). + WithSpec(applyconfigv1alpha1.RouterSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testRouterImport() *applyconfigv1alpha1.RouterImportApplyConfiguration { + return applyconfigv1alpha1.RouterImport().WithID(routerID) +} + +var _ = Describe("ORC Router API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal router and managementPolicy should default to managed", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithResource(testRouterResource()) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + Expect(router.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should have immutable externalGateways", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithExternalGateways(applyconfigv1alpha1.ExternalGateway().WithNetworkRef("net-a"))) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithExternalGateways(applyconfigv1alpha1.ExternalGateway().WithNetworkRef("net-b"))) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("externalGateways is immutable"))) + }) + + It("should have immutable distributed", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithDistributed(true)) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithDistributed(false)) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("distributed is immutable"))) + }) + + It("should have immutable projectRef", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithProjectRef("project-a")) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithProjectRef("project-b")) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should reject duplicate tags", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithResource(applyconfigv1alpha1.RouterResourceSpec(). + WithTags("foo", "bar", "foo")) + Expect(applyObj(ctx, router, patch)).NotTo(Succeed()) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testRouterImport()) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testRouterImport()). + WithResource(testRouterResource()) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.RouterImport()) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.RouterImport(). + WithFilter(applyconfigv1alpha1.RouterFilter())) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.RouterImport(). + WithFilter(applyconfigv1alpha1.RouterFilter().WithName("foo"))) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testRouterResource()) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec. + WithImport(testRouterImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testRouterResource()) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec. + WithImport(testRouterImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + router := routerStub(namespace) + patch := baseRouterPatch(router) + patch.Spec.WithResource(testRouterResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, router, patch)).To(Succeed()) + Expect(router.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From bd2c8896e045997c2625f085eea6b63498c173a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:34:23 +0100 Subject: [PATCH 084/121] test: add API validation tests for Trunk --- test/apivalidations/trunk_test.go | 241 ++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 test/apivalidations/trunk_test.go diff --git a/test/apivalidations/trunk_test.go b/test/apivalidations/trunk_test.go new file mode 100644 index 000000000..506eb4329 --- /dev/null +++ b/test/apivalidations/trunk_test.go @@ -0,0 +1,241 @@ +/* +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 ( + trunkName = "trunk" + trunkID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae133" +) + +func trunkStub(namespace *corev1.Namespace) *orcv1alpha1.Trunk { + obj := &orcv1alpha1.Trunk{} + obj.Name = trunkName + obj.Namespace = namespace.Name + return obj +} + +func testTrunkResource() *applyconfigv1alpha1.TrunkResourceSpecApplyConfiguration { + return applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port") +} + +func baseTrunkPatch(trunk client.Object) *applyconfigv1alpha1.TrunkApplyConfiguration { + return applyconfigv1alpha1.Trunk(trunk.GetName(), trunk.GetNamespace()). + WithSpec(applyconfigv1alpha1.TrunkSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testTrunkImport() *applyconfigv1alpha1.TrunkImportApplyConfiguration { + return applyconfigv1alpha1.TrunkImport().WithID(trunkID) +} + +var _ = Describe("ORC Trunk API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal trunk and managementPolicy should default to managed", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(testTrunkResource()) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + Expect(trunk.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject a trunk without required field portRef", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec()) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.resource.portRef"))) + }) + + It("should have immutable portRef", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("port-a")) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("port-b")) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("portRef is immutable"))) + }) + + It("should have immutable projectRef", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port"). + WithProjectRef("project-a")) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port"). + WithProjectRef("project-b")) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should reject invalid segmentationType enum value in subport", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port"). + WithSubports(applyconfigv1alpha1.TrunkSubportSpec(). + WithPortRef("sub-port"). + WithSegmentationID(100). + WithSegmentationType("invalid"))) + Expect(applyObj(ctx, trunk, patch)).NotTo(Succeed()) + }) + + It("should permit valid segmentationType enum values in subport", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port"). + WithSubports(applyconfigv1alpha1.TrunkSubportSpec(). + WithPortRef("sub-port"). + WithSegmentationID(100). + WithSegmentationType("vlan"))) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + }) + + It("should reject segmentationID out of range in subport", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + + // Below minimum + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port"). + WithSubports(applyconfigv1alpha1.TrunkSubportSpec(). + WithPortRef("sub-port"). + WithSegmentationID(0). + WithSegmentationType("vlan"))) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.resource.subports[0].segmentationID"))) + + // Above maximum + patch.Spec.WithResource(applyconfigv1alpha1.TrunkResourceSpec(). + WithPortRef("my-port"). + WithSubports(applyconfigv1alpha1.TrunkSubportSpec(). + WithPortRef("sub-port"). + WithSegmentationID(4095). + WithSegmentationType("vlan"))) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.resource.subports[0].segmentationID"))) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testTrunkImport()) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testTrunkImport()). + WithResource(testTrunkResource()) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.TrunkImport()) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.TrunkImport(). + WithFilter(applyconfigv1alpha1.TrunkFilter())) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.TrunkImport(). + WithFilter(applyconfigv1alpha1.TrunkFilter().WithName("foo"))) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testTrunkResource()) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec. + WithImport(testTrunkImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testTrunkResource()) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec. + WithImport(testTrunkImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + trunk := trunkStub(namespace) + patch := baseTrunkPatch(trunk) + patch.Spec.WithResource(testTrunkResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, trunk, patch)).To(Succeed()) + Expect(trunk.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From 617feb7226ec411300f787f8b83912ce67da8b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 12 Mar 2026 17:34:23 +0100 Subject: [PATCH 085/121] test: add API validation tests for Server --- test/apivalidations/server_test.go | 285 +++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 test/apivalidations/server_test.go diff --git a/test/apivalidations/server_test.go b/test/apivalidations/server_test.go new file mode 100644 index 000000000..789f03b09 --- /dev/null +++ b/test/apivalidations/server_test.go @@ -0,0 +1,285 @@ +/* +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 ( + serverName = "server" + serverID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae134" +) + +func serverStub(namespace *corev1.Namespace) *orcv1alpha1.Server { + obj := &orcv1alpha1.Server{} + obj.Name = serverName + obj.Namespace = namespace.Name + return obj +} + +func testServerResource() *applyconfigv1alpha1.ServerResourceSpecApplyConfiguration { + return applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")) +} + +func baseServerPatch(server client.Object) *applyconfigv1alpha1.ServerApplyConfiguration { + return applyconfigv1alpha1.Server(server.GetName(), server.GetNamespace()). + WithSpec(applyconfigv1alpha1.ServerSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testServerImport() *applyconfigv1alpha1.ServerImportApplyConfiguration { + return applyconfigv1alpha1.ServerImport().WithID(serverID) +} + +var _ = Describe("ORC Server API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal server and managementPolicy should default to managed", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(testServerResource()) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + Expect(server.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should reject a server without required fields", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec()) + Expect(applyObj(ctx, server, patch)).NotTo(Succeed()) + + // Missing flavorRef + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.resource.flavorRef"))) + + // Missing imageRef + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.resource.imageRef"))) + + // Missing ports + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor")) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.resource.ports"))) + }) + + It("should have immutable imageRef", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("image-a"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("image-b"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("imageRef is immutable"))) + }) + + It("should have immutable flavorRef", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("flavor-a"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("flavor-b"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("flavorRef is immutable"))) + }) + + It("should have immutable serverGroupRef", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithServerGroupRef("sg-a")) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithServerGroupRef("sg-b")) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("serverGroupRef is immutable"))) + }) + + It("should have immutable keypairRef", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithKeypairRef("kp-a")) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithKeypairRef("kp-b")) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("keypairRef is immutable"))) + }) + + It("should have immutable configDrive", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithConfigDrive(true)) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithConfigDrive(false)) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("configDrive is immutable"))) + }) + + It("should reject duplicate tags", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). + WithImageRef("my-image"). + WithFlavorRef("my-flavor"). + WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port")). + WithTags("foo", "bar", "foo")) + Expect(applyObj(ctx, server, patch)).NotTo(Succeed()) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + patch.Spec.WithImport(testServerImport()) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(testServerImport()). + WithResource(testServerResource()) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServerImport()) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServerImport(). + WithFilter(applyconfigv1alpha1.ServerFilter())) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit import filter with name", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ServerImport(). + WithFilter(applyconfigv1alpha1.ServerFilter().WithName("foo"))) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + patch.Spec.WithResource(testServerResource()) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec. + WithImport(testServerImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). + WithResource(testServerResource()) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec. + WithImport(testServerImport()). + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + server := serverStub(namespace) + patch := baseServerPatch(server) + patch.Spec.WithResource(testServerResource()). + WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). + WithOnDelete(orcv1alpha1.OnDeleteDetach)) + Expect(applyObj(ctx, server, patch)).To(Succeed()) + Expect(server.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +}) From a792eecfe910974c11291e5317ef0db0d74be889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 13 Mar 2026 11:28:11 +0100 Subject: [PATCH 086/121] test: extract shared management policy validation tests Extract duplicated management policy validation tests from all 22 resource test files into a generic runManagementPolicyTests helper in common_test.go. This reduces ~100 lines of duplicated test code per file to ~13 lines of resource-specific callbacks. The shared helper covers all standard management policy validations: - Default management policy is managed - Require import for unmanaged - Not permit unmanaged with resource - Not permit empty import - Not permit empty import filter - Permit valid import filter - Require resource for managed - Not permit managed with import - Not permit managedOptions for unmanaged - Permit managedOptions for managed For network, port, and subnet, this also adds previously missing management policy test coverage. Disable the dupl linter for test/ since the remaining per-resource callback boilerplate is inherently similar. --- .golangci.yml | 3 + test/apivalidations/common_test.go | 131 +++++++++++++++++++ test/apivalidations/domain_test.go | 128 +++++------------- test/apivalidations/endpoint_test.go | 125 +++++------------- test/apivalidations/flavor_test.go | 152 +++++----------------- test/apivalidations/floatingip_test.go | 131 +++++-------------- test/apivalidations/group_test.go | 133 +++++-------------- test/apivalidations/image_test.go | 145 ++++++--------------- test/apivalidations/keypair_test.go | 125 +++++------------- test/apivalidations/network_test.go | 59 ++++++--- test/apivalidations/port_test.go | 49 ++++++- test/apivalidations/project_test.go | 125 +++++------------- test/apivalidations/role_test.go | 133 +++++-------------- test/apivalidations/router_test.go | 131 +++++-------------- test/apivalidations/securitygroup_test.go | 133 +++++-------------- test/apivalidations/server_test.go | 131 +++++-------------- test/apivalidations/servergroup_test.go | 129 +++++------------- test/apivalidations/service_test.go | 125 +++++------------- test/apivalidations/subnet_test.go | 68 +++++++--- test/apivalidations/trunk_test.go | 131 +++++-------------- test/apivalidations/user_test.go | 125 +++++------------- test/apivalidations/volume_test.go | 131 +++++-------------- test/apivalidations/volumetype_test.go | 129 +++++------------- 23 files changed, 867 insertions(+), 1805 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 0adca1fc8..cf5891f9c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -84,6 +84,9 @@ linters: - dupl - lll path: internal/* + - linters: + - dupl + path: test/* - linters: - dupl - goimports diff --git a/test/apivalidations/common_test.go b/test/apivalidations/common_test.go index b71099b91..8300b90f4 100644 --- a/test/apivalidations/common_test.go +++ b/test/apivalidations/common_test.go @@ -17,6 +17,14 @@ 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" ) @@ -25,3 +33,126 @@ func testCredentials() *applyconfigv1alpha1.CloudCredentialsReferenceApplyConfig WithSecretName("openstack-credentials"). WithCloudName("openstack") } + +// managementPolicyTestArgs provides resource-specific callbacks for the shared +// management policy validation tests. PatchT is the concrete apply +// configuration type for the resource (e.g. *applyconfigv1alpha1.FlavorApplyConfiguration). +type managementPolicyTestArgs[PatchT any] struct { + // createObject returns a new stub object in the given namespace. + createObject func(*corev1.Namespace) client.Object + // basePatch returns a patch with only cloudCredentialsRef set. + basePatch func(client.Object) PatchT + // applyResource adds a valid resource spec to the patch. + applyResource func(PatchT) + // applyImport adds a valid import (by ID) to the patch. + applyImport func(PatchT) + // applyEmptyImport adds an empty import to the patch. + applyEmptyImport func(PatchT) + // applyEmptyFilter adds an import with an empty filter to the patch. + applyEmptyFilter func(PatchT) + // applyValidFilter adds an import with a valid filter to the patch. + applyValidFilter func(PatchT) + // applyManaged sets the management policy to managed. + applyManaged func(PatchT) + // applyUnmanaged sets the management policy to unmanaged. + applyUnmanaged func(PatchT) + // applyManagedOptions adds managedOptions to the patch. + applyManagedOptions func(PatchT) + // getManagementPolicy reads the management policy from the object. + getManagementPolicy func(client.Object) orcv1alpha1.ManagementPolicy + // getOnDelete reads the onDelete value from the object's managedOptions. + getOnDelete func(client.Object) orcv1alpha1.OnDelete +} + +// runManagementPolicyTests registers shared Ginkgo test cases for the standard +// management policy validations that apply to all ORC resources with a +// managementPolicy field. +func runManagementPolicyTests[PatchT any](getNamespace func() *corev1.Namespace, args managementPolicyTestArgs[PatchT]) { + It("should allow to create a minimal resource and managementPolicy should default to managed", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyResource(patch) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + Expect(args.getManagementPolicy(obj)).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should require import for unmanaged", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyUnmanaged(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) + + args.applyImport(patch) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + }) + + It("should not permit unmanaged with resource", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyUnmanaged(patch) + args.applyImport(patch) + args.applyResource(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) + }) + + It("should not permit empty import", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyUnmanaged(patch) + args.applyEmptyImport(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyUnmanaged(patch) + args.applyEmptyFilter(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) + }) + + It("should permit valid import filter", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyUnmanaged(patch) + args.applyValidFilter(patch) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + }) + + It("should require resource for managed", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyManaged(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) + + args.applyResource(patch) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + }) + + It("should not permit managed with import", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyImport(patch) + args.applyManaged(patch) + args.applyResource(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) + }) + + It("should not permit managedOptions for unmanaged", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyImport(patch) + args.applyUnmanaged(patch) + args.applyManagedOptions(patch) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) + }) + + It("should permit managedOptions for managed", func(ctx context.Context) { + obj := args.createObject(getNamespace()) + patch := args.basePatch(obj) + args.applyResource(patch) + args.applyManagedOptions(patch) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + Expect(args.getOnDelete(obj)).To(Equal(orcv1alpha1.OnDelete("detach"))) + }) +} diff --git a/test/apivalidations/domain_test.go b/test/apivalidations/domain_test.go index 43d61da72..4087a60ed 100644 --- a/test/apivalidations/domain_test.go +++ b/test/apivalidations/domain_test.go @@ -17,10 +17,7 @@ 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" @@ -60,101 +57,34 @@ var _ = Describe("ORC Domain API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal domain and managementPolicy should default to managed", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec.WithResource(testDomainResource()) - Expect(applyObj(ctx, domain, patch)).To(Succeed()) - Expect(domain.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) - }) - - It("should require import for unmanaged", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testDomainImport()) - Expect(applyObj(ctx, domain, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testDomainImport()). - WithResource(testDomainResource()) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.DomainImport()) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.DomainImport(). - WithFilter(applyconfigv1alpha1.DomainFilter())) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.DomainImport(). - WithFilter(applyconfigv1alpha1.DomainFilter().WithName("foo"))) - Expect(applyObj(ctx, domain, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testDomainResource()) - Expect(applyObj(ctx, domain, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec. - WithImport(testDomainImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testDomainResource()) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec. - WithImport(testDomainImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, domain, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - domain := domainStub(namespace) - patch := baseDomainPatch(domain) - patch.Spec.WithResource(testDomainResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, domain, patch)).To(Succeed()) - Expect(domain.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.DomainApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return domainStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.DomainApplyConfiguration { return baseDomainPatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { p.Spec.WithResource(testDomainResource()) }, + applyImport: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { p.Spec.WithImport(testDomainImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.DomainImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.DomainImport().WithFilter(applyconfigv1alpha1.DomainFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.DomainImport().WithFilter(applyconfigv1alpha1.DomainFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.DomainApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Domain).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Domain).Spec.ManagedOptions.OnDelete + }, }) }) diff --git a/test/apivalidations/endpoint_test.go b/test/apivalidations/endpoint_test.go index 666324996..b648c66f1 100644 --- a/test/apivalidations/endpoint_test.go +++ b/test/apivalidations/endpoint_test.go @@ -63,12 +63,35 @@ var _ = Describe("ORC Endpoint API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal endpoint and managementPolicy should default to managed", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec.WithResource(testEndpointResource()) - Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) - Expect(endpoint.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.EndpointApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return endpointStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.EndpointApplyConfiguration { return baseEndpointPatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { p.Spec.WithResource(testEndpointResource()) }, + applyImport: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { p.Spec.WithImport(testEndpointImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.EndpointImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.EndpointImport().WithFilter(applyconfigv1alpha1.EndpointFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.EndpointImport().WithFilter(applyconfigv1alpha1.EndpointFilter().WithInterface("public"))) + }, + applyManaged: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.EndpointApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Endpoint).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Endpoint).Spec.ManagedOptions.OnDelete + }, }) It("should reject an endpoint without required fields", func(ctx context.Context) { @@ -148,94 +171,4 @@ var _ = Describe("ORC Endpoint API validations", func() { WithDescription("desc-b")) Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("description is immutable"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testEndpointImport()) - Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testEndpointImport()). - WithResource(testEndpointResource()) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.EndpointImport()) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.EndpointImport(). - WithFilter(applyconfigv1alpha1.EndpointFilter())) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with interface", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.EndpointImport(). - WithFilter(applyconfigv1alpha1.EndpointFilter().WithInterface("public"))) - Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testEndpointResource()) - Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec. - WithImport(testEndpointImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testEndpointResource()) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec. - WithImport(testEndpointImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, endpoint, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - endpoint := endpointStub(namespace) - patch := baseEndpointPatch(endpoint) - patch.Spec.WithResource(testEndpointResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, endpoint, patch)).To(Succeed()) - Expect(endpoint.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/flavor_test.go b/test/apivalidations/flavor_test.go index 322050abe..dd30e2f10 100644 --- a/test/apivalidations/flavor_test.go +++ b/test/apivalidations/flavor_test.go @@ -51,12 +51,6 @@ func baseFlavorPatch(flavor client.Object) *applyconfigv1alpha1.FlavorApplyConfi WithCloudCredentialsRef(testCredentials())) } -func baseWorkingFlavorPatch(flavor client.Object) *applyconfigv1alpha1.FlavorApplyConfiguration { - patch := baseFlavorPatch(flavor) - patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithRAM(1).WithVcpus(1).WithDisk(1)) - return patch -} - func testFlavorImport() *applyconfigv1alpha1.FlavorImportApplyConfiguration { return applyconfigv1alpha1.FlavorImport().WithID(flavorID) } @@ -67,12 +61,41 @@ var _ = Describe("ORC Flavor API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal flavor and managementPolicy should default to managed", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithRAM(1).WithVcpus(1).WithDisk(1)) - Expect(applyObj(ctx, flavor, patch)).To(Succeed()) - Expect(flavor.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.FlavorApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return flavorStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.FlavorApplyConfiguration { + return baseFlavorPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithResource(testFlavorResource()) + }, + applyImport: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithImport(testFlavorImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.FlavorImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.FlavorImport().WithFilter(applyconfigv1alpha1.FlavorFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.FlavorImport().WithFilter(applyconfigv1alpha1.FlavorFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.FlavorApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Flavor).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Flavor).Spec.ManagedOptions.OnDelete + }, }) It("should be immutable", func(ctx context.Context) { @@ -116,70 +139,6 @@ var _ = Describe("ORC Flavor API validations", func() { Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.resource.description: Too long"))) }) - It("should default to managementPolicy managed", func(ctx context.Context) { - flavor := flavorStub(namespace) - flavor.Spec.Resource = &orcv1alpha1.FlavorResourceSpec{ - RAM: 1, - Vcpus: 1, - } - flavor.Spec.CloudCredentialsRef = orcv1alpha1.CloudCredentialsReference{ - SecretName: "my-secret", - CloudName: "my-cloud", - } - - Expect(k8sClient.Create(ctx, flavor)).To(Succeed()) - Expect(flavor.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) - }) - - It("should require import for unmanaged", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testFlavorImport()) - Expect(applyObj(ctx, flavor, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testFlavorImport()). - WithResource(testFlavorResource()) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.FlavorImport()) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.FlavorImport(). - WithFilter(applyconfigv1alpha1.FlavorFilter())) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with values within bound", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.FlavorImport(). - WithFilter(applyconfigv1alpha1.FlavorFilter(). - WithName("foo").WithRAM(1))) - Expect(applyObj(ctx, flavor, patch)).To(Succeed()) - }) It("should reject import filter with value less than minimal", func(ctx context.Context) { flavor := flavorStub(namespace) @@ -190,45 +149,4 @@ var _ = Describe("ORC Flavor API validations", func() { WithFilter(applyconfigv1alpha1.FlavorFilter().WithRAM(0))) Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.import.filter.ram in body should be greater than or equal to 1"))) }) - - It("should require resource for managed", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testFlavorResource()) - Expect(applyObj(ctx, flavor, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec. - WithImport(testFlavorImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testFlavorResource()) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseFlavorPatch(flavor) - patch.Spec. - WithImport(testFlavorImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - flavor := flavorStub(namespace) - patch := baseWorkingFlavorPatch(flavor) - patch.Spec. - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, flavor, patch)).To(Succeed()) - Expect(flavor.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/floatingip_test.go b/test/apivalidations/floatingip_test.go index 3c9b20f25..58295cf2f 100644 --- a/test/apivalidations/floatingip_test.go +++ b/test/apivalidations/floatingip_test.go @@ -61,12 +61,41 @@ var _ = Describe("ORC FloatingIP API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal floatingip and managementPolicy should default to managed", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec.WithResource(testFloatingIPResource()) - Expect(applyObj(ctx, fip, patch)).To(Succeed()) - Expect(fip.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.FloatingIPApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return floatingIPStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.FloatingIPApplyConfiguration { + return baseFloatingIPPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithResource(testFloatingIPResource()) + }, + applyImport: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithImport(testFloatingIPImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.FloatingIPImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.FloatingIPImport().WithFilter(applyconfigv1alpha1.FloatingIPFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.FloatingIPImport().WithFilter(applyconfigv1alpha1.FloatingIPFilter().WithFloatingNetworkRef("my-network"))) + }, + applyManaged: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.FloatingIPApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.FloatingIP).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.FloatingIP).Spec.ManagedOptions.OnDelete + }, }) It("should require exactly one of floatingNetworkRef or floatingSubnetRef", func(ctx context.Context) { @@ -140,94 +169,4 @@ var _ = Describe("ORC FloatingIP API validations", func() { WithProjectRef("project-b")) Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testFloatingIPImport()) - Expect(applyObj(ctx, fip, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testFloatingIPImport()). - WithResource(testFloatingIPResource()) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.FloatingIPImport()) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.FloatingIPImport(). - WithFilter(applyconfigv1alpha1.FloatingIPFilter())) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with floatingNetworkRef", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.FloatingIPImport(). - WithFilter(applyconfigv1alpha1.FloatingIPFilter().WithFloatingNetworkRef("my-network"))) - Expect(applyObj(ctx, fip, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testFloatingIPResource()) - Expect(applyObj(ctx, fip, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec. - WithImport(testFloatingIPImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testFloatingIPResource()) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec. - WithImport(testFloatingIPImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, fip, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - fip := floatingIPStub(namespace) - patch := baseFloatingIPPatch(fip) - patch.Spec.WithResource(testFloatingIPResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, fip, patch)).To(Succeed()) - Expect(fip.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/group_test.go b/test/apivalidations/group_test.go index 1e582f43e..e322615ae 100644 --- a/test/apivalidations/group_test.go +++ b/test/apivalidations/group_test.go @@ -60,103 +60,42 @@ var _ = Describe("ORC Group API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal group and managementPolicy should default to managed", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec.WithResource(testGroupResource()) - Expect(applyObj(ctx, group, patch)).To(Succeed()) - Expect(group.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) - }) - - It("should require import for unmanaged", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testGroupImport()) - Expect(applyObj(ctx, group, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testGroupImport()). - WithResource(testGroupResource()) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.GroupImport()) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.GroupImport(). - WithFilter(applyconfigv1alpha1.GroupFilter())) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.GroupImport(). - WithFilter(applyconfigv1alpha1.GroupFilter().WithName("foo"))) - Expect(applyObj(ctx, group, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testGroupResource()) - Expect(applyObj(ctx, group, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec. - WithImport(testGroupImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testGroupResource()) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec. - WithImport(testGroupImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, group, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - group := groupStub(namespace) - patch := baseGroupPatch(group) - patch.Spec.WithResource(testGroupResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, group, patch)).To(Succeed()) - Expect(group.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, + managementPolicyTestArgs[*applyconfigv1alpha1.GroupApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return groupStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.GroupApplyConfiguration { return baseGroupPatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithResource(testGroupResource()) + }, + applyImport: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithImport(testGroupImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.GroupImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.GroupImport().WithFilter(applyconfigv1alpha1.GroupFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.GroupImport().WithFilter(applyconfigv1alpha1.GroupFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.GroupApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Group).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Group).Spec.ManagedOptions.OnDelete + }, + }, + ) It("should have immutable domainRef", func(ctx context.Context) { group := groupStub(namespace) diff --git a/test/apivalidations/image_test.go b/test/apivalidations/image_test.go index e9159b580..3d192fb84 100644 --- a/test/apivalidations/image_test.go +++ b/test/apivalidations/image_test.go @@ -108,6 +108,43 @@ var _ = Describe("ORC Image API validations", func() { namespace = createNamespace() }) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.ImageApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return imageStub("image", ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.ImageApplyConfiguration { + return basePatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithResource(testImageResource()) + }, + applyImport: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithImport(testImageImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ImageImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ImageImport().WithFilter(applyconfigv1alpha1.ImageFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ImageImport().WithFilter(applyconfigv1alpha1.ImageFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.ImageApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Image).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Image).Spec.ManagedOptions.OnDelete + }, + }) + It("should allow to create a minimal image", func(ctx context.Context) { image := imageStub("image", namespace) minimalPatch := minimalManagedPatch(image) @@ -115,93 +152,6 @@ var _ = Describe("ORC Image API validations", func() { Expect(applyObj(ctx, image, minimalPatch)).To(Succeed()) }) - It("should default to managementPolicy managed", func(ctx context.Context) { - image := imageStub("image", namespace) - image.Spec.Resource = &orcv1alpha1.ImageResourceSpec{ - Content: &orcv1alpha1.ImageContent{ - DiskFormat: orcv1alpha1.ImageDiskFormatQCOW2, - Download: &orcv1alpha1.ImageContentSourceDownload{ - URL: "https://example.com/example.img", - }, - }, - } - image.Spec.CloudCredentialsRef = orcv1alpha1.CloudCredentialsReference{ - SecretName: "my-secret", - CloudName: "my-cloud", - } - - Expect(k8sClient.Create(ctx, image)).To(Succeed()) - Expect(image.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) - }) - - It("should require import for unmanaged", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) - - patch.Spec.WithImport(testImageImport()) - Expect(applyObj(ctx, image, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testImageImport()). - WithResource(testImageResource()) - }) - - It("should not permit empty import", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ImageImport()) - Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ImageImport(). - WithFilter(applyconfigv1alpha1.ImageFilter())) - Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) - }) - - It("should permit import filter with name", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ImageImport(). - WithFilter(applyconfigv1alpha1.ImageFilter().WithName("foo"))) - Expect(applyObj(ctx, image, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) - - patch.Spec.WithResource(testImageResource()) - Expect(applyObj(ctx, image, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec. - WithImport(testImageImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testImageResource()) - Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) - }) - It("should require content when not importing", func(ctx context.Context) { image := imageStub("image", namespace) patch := minimalManagedPatch(image) @@ -209,27 +159,6 @@ var _ = Describe("ORC Image API validations", func() { Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) }) - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := basePatch(image) - patch.Spec. - WithImport(testImageImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, image, patch)).NotTo(Succeed()) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - image := imageStub("image", namespace) - patch := minimalManagedPatch(image) - patch.Spec. - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, image, patch)).To(Succeed()) - Expect(image.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) - DescribeTable("should permit containerFormat", func(ctx context.Context, containerFormat orcv1alpha1.ImageContainerFormat) { image := imageStub("image", namespace) diff --git a/test/apivalidations/keypair_test.go b/test/apivalidations/keypair_test.go index d5b57586b..bb0e9f618 100644 --- a/test/apivalidations/keypair_test.go +++ b/test/apivalidations/keypair_test.go @@ -61,12 +61,35 @@ var _ = Describe("ORC KeyPair API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal keypair and managementPolicy should default to managed", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec.WithResource(testKeypairResource()) - Expect(applyObj(ctx, keypair, patch)).To(Succeed()) - Expect(keypair.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.KeyPairApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return keypairStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.KeyPairApplyConfiguration { return baseKeypairPatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { p.Spec.WithResource(testKeypairResource()) }, + applyImport: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { p.Spec.WithImport(testKeypairImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.KeyPairImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.KeyPairImport().WithFilter(applyconfigv1alpha1.KeyPairFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.KeyPairImport().WithFilter(applyconfigv1alpha1.KeyPairFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.KeyPairApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.KeyPair).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.KeyPair).Spec.ManagedOptions.OnDelete + }, }) It("should reject a keypair without required field publicKey", func(ctx context.Context) { @@ -101,94 +124,4 @@ var _ = Describe("ORC KeyPair API validations", func() { WithPublicKey(strings.Repeat("a", 16385))) Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.resource.publicKey"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testKeypairImport()) - Expect(applyObj(ctx, keypair, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testKeypairImport()). - WithResource(testKeypairResource()) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.KeyPairImport()) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.KeyPairImport(). - WithFilter(applyconfigv1alpha1.KeyPairFilter())) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.KeyPairImport(). - WithFilter(applyconfigv1alpha1.KeyPairFilter().WithName("foo"))) - Expect(applyObj(ctx, keypair, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testKeypairResource()) - Expect(applyObj(ctx, keypair, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec. - WithImport(testKeypairImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testKeypairResource()) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec. - WithImport(testKeypairImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, keypair, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - keypair := keypairStub(namespace) - patch := baseKeypairPatch(keypair) - patch.Spec.WithResource(testKeypairResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, keypair, patch)).To(Succeed()) - Expect(keypair.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/network_test.go b/test/apivalidations/network_test.go index 58cfb0ebb..7ca8e925e 100644 --- a/test/apivalidations/network_test.go +++ b/test/apivalidations/network_test.go @@ -40,6 +40,14 @@ func networkStub(namespace *corev1.Namespace) *orcv1alpha1.Network { return obj } +func testNetworkResource() *applyconfigv1alpha1.NetworkResourceSpecApplyConfiguration { + return applyconfigv1alpha1.NetworkResourceSpec() +} + +func testNetworkImport() *applyconfigv1alpha1.NetworkImportApplyConfiguration { + return applyconfigv1alpha1.NetworkImport().WithID(networkID) +} + func baseNetworkPatch(network client.Object) *applyconfigv1alpha1.NetworkApplyConfiguration { return applyconfigv1alpha1.Network(network.GetName(), network.GetNamespace()). WithSpec(applyconfigv1alpha1.NetworkSpec(). @@ -52,12 +60,41 @@ var _ = Describe("ORC Network API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal network and managementPolicy should default to managed", func(ctx context.Context) { - network := networkStub(namespace) - patch := baseNetworkPatch(network) - patch.Spec.WithResource(applyconfigv1alpha1.NetworkResourceSpec()) - Expect(applyObj(ctx, network, patch)).To(Succeed()) - Expect(network.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.NetworkApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return networkStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.NetworkApplyConfiguration { + return baseNetworkPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithResource(testNetworkResource()) + }, + applyImport: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithImport(testNetworkImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.NetworkImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.NetworkImport().WithFilter(applyconfigv1alpha1.NetworkFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.NetworkImport().WithFilter(applyconfigv1alpha1.NetworkFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.NetworkApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Network).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Network).Spec.ManagedOptions.OnDelete + }, }) DescribeTable("should permit valid DNS domain", @@ -152,16 +189,6 @@ var _ = Describe("ORC Network API validations", func() { Expect(applyObj(ctx, network, patch)).To(Succeed()) }) - It("should not permit empty import filter", func(ctx context.Context) { - network := networkStub(namespace) - patch := baseNetworkPatch(network) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.NetworkImport(). - WithFilter(applyconfigv1alpha1.NetworkFilter())) - Expect(applyObj(ctx, network, patch)).NotTo(Succeed()) - }) - It("should not permit invalid import filter", func(ctx context.Context) { network := networkStub(namespace) patch := baseNetworkPatch(network) diff --git a/test/apivalidations/port_test.go b/test/apivalidations/port_test.go index 61ef31e00..6c963f4dd 100644 --- a/test/apivalidations/port_test.go +++ b/test/apivalidations/port_test.go @@ -42,6 +42,14 @@ func portStub(namespace *corev1.Namespace) *orcv1alpha1.Port { return obj } +func testPortResource() *applyconfigv1alpha1.PortResourceSpecApplyConfiguration { + return applyconfigv1alpha1.PortResourceSpec().WithNetworkRef(networkName) +} + +func testPortImport() *applyconfigv1alpha1.PortImportApplyConfiguration { + return applyconfigv1alpha1.PortImport().WithID(portID) +} + func basePortPatch(port client.Object) *applyconfigv1alpha1.PortApplyConfiguration { return applyconfigv1alpha1.Port(port.GetName(), port.GetNamespace()). WithSpec(applyconfigv1alpha1.PortSpec(). @@ -54,12 +62,41 @@ var _ = Describe("ORC Port API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal port and managementPolicy should default to managed", func(ctx context.Context) { - port := portStub(namespace) - patch := basePortPatch(port) - patch.Spec.WithResource(applyconfigv1alpha1.PortResourceSpec().WithNetworkRef(networkName)) - Expect(applyObj(ctx, port, patch)).To(Succeed()) - Expect(port.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.PortApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return portStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.PortApplyConfiguration { + return basePortPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithResource(testPortResource()) + }, + applyImport: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithImport(testPortImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.PortImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.PortImport().WithFilter(applyconfigv1alpha1.PortFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.PortImport().WithFilter(applyconfigv1alpha1.PortFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.PortApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Port).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Port).Spec.ManagedOptions.OnDelete + }, }) It("should allow to create a port with securityGroupRefs when portSecurity is enabled", func(ctx context.Context) { diff --git a/test/apivalidations/project_test.go b/test/apivalidations/project_test.go index 23f76b84a..383f817c5 100644 --- a/test/apivalidations/project_test.go +++ b/test/apivalidations/project_test.go @@ -60,12 +60,35 @@ var _ = Describe("ORC Project API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal project and managementPolicy should default to managed", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec.WithResource(testProjectResource()) - Expect(applyObj(ctx, project, patch)).To(Succeed()) - Expect(project.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.ProjectApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return projectStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.ProjectApplyConfiguration { return baseProjectPatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { p.Spec.WithResource(testProjectResource()) }, + applyImport: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { p.Spec.WithImport(testProjectImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ProjectImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ProjectImport().WithFilter(applyconfigv1alpha1.ProjectFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ProjectImport().WithFilter(applyconfigv1alpha1.ProjectFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.ProjectApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Project).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Project).Spec.ManagedOptions.OnDelete + }, }) It("should reject duplicate tags", func(ctx context.Context) { @@ -83,94 +106,4 @@ var _ = Describe("ORC Project API validations", func() { WithTags("foo", "bar")) Expect(applyObj(ctx, project, patch)).To(Succeed()) }) - - It("should require import for unmanaged", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testProjectImport()) - Expect(applyObj(ctx, project, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testProjectImport()). - WithResource(testProjectResource()) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ProjectImport()) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ProjectImport(). - WithFilter(applyconfigv1alpha1.ProjectFilter())) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ProjectImport(). - WithFilter(applyconfigv1alpha1.ProjectFilter().WithName("foo"))) - Expect(applyObj(ctx, project, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testProjectResource()) - Expect(applyObj(ctx, project, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec. - WithImport(testProjectImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testProjectResource()) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec. - WithImport(testProjectImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - project := projectStub(namespace) - patch := baseProjectPatch(project) - patch.Spec.WithResource(testProjectResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, project, patch)).To(Succeed()) - Expect(project.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/role_test.go b/test/apivalidations/role_test.go index 019543acf..638dad6d1 100644 --- a/test/apivalidations/role_test.go +++ b/test/apivalidations/role_test.go @@ -60,103 +60,42 @@ var _ = Describe("ORC Role API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal role and managementPolicy should default to managed", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec.WithResource(testRoleResource()) - Expect(applyObj(ctx, role, patch)).To(Succeed()) - Expect(role.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) - }) - - It("should require import for unmanaged", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testRoleImport()) - Expect(applyObj(ctx, role, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testRoleImport()). - WithResource(testRoleResource()) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.RoleImport()) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.RoleImport(). - WithFilter(applyconfigv1alpha1.RoleFilter())) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.RoleImport(). - WithFilter(applyconfigv1alpha1.RoleFilter().WithName("foo"))) - Expect(applyObj(ctx, role, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testRoleResource()) - Expect(applyObj(ctx, role, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec. - WithImport(testRoleImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testRoleResource()) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec. - WithImport(testRoleImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, role, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - role := roleStub(namespace) - patch := baseRolePatch(role) - patch.Spec.WithResource(testRoleResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, role, patch)).To(Succeed()) - Expect(role.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, + managementPolicyTestArgs[*applyconfigv1alpha1.RoleApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return roleStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.RoleApplyConfiguration { return baseRolePatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithResource(testRoleResource()) + }, + applyImport: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithImport(testRoleImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleImport().WithFilter(applyconfigv1alpha1.RoleFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleImport().WithFilter(applyconfigv1alpha1.RoleFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.RoleApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Role).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Role).Spec.ManagedOptions.OnDelete + }, + }, + ) It("should have immutable domainRef", func(ctx context.Context) { role := roleStub(namespace) diff --git a/test/apivalidations/router_test.go b/test/apivalidations/router_test.go index 57e454236..f574b8afa 100644 --- a/test/apivalidations/router_test.go +++ b/test/apivalidations/router_test.go @@ -60,12 +60,41 @@ var _ = Describe("ORC Router API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal router and managementPolicy should default to managed", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec.WithResource(testRouterResource()) - Expect(applyObj(ctx, router, patch)).To(Succeed()) - Expect(router.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.RouterApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return routerStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.RouterApplyConfiguration { + return baseRouterPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithResource(testRouterResource()) + }, + applyImport: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithImport(testRouterImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RouterImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RouterImport().WithFilter(applyconfigv1alpha1.RouterFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RouterImport().WithFilter(applyconfigv1alpha1.RouterFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.RouterApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Router).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Router).Spec.ManagedOptions.OnDelete + }, }) It("should have immutable externalGateways", func(ctx context.Context) { @@ -111,94 +140,4 @@ var _ = Describe("ORC Router API validations", func() { WithTags("foo", "bar", "foo")) Expect(applyObj(ctx, router, patch)).NotTo(Succeed()) }) - - It("should require import for unmanaged", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testRouterImport()) - Expect(applyObj(ctx, router, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testRouterImport()). - WithResource(testRouterResource()) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.RouterImport()) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.RouterImport(). - WithFilter(applyconfigv1alpha1.RouterFilter())) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.RouterImport(). - WithFilter(applyconfigv1alpha1.RouterFilter().WithName("foo"))) - Expect(applyObj(ctx, router, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testRouterResource()) - Expect(applyObj(ctx, router, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec. - WithImport(testRouterImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testRouterResource()) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec. - WithImport(testRouterImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, router, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - router := routerStub(namespace) - patch := baseRouterPatch(router) - patch.Spec.WithResource(testRouterResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, router, patch)).To(Succeed()) - Expect(router.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/securitygroup_test.go b/test/apivalidations/securitygroup_test.go index 732c5493c..d55ce6560 100644 --- a/test/apivalidations/securitygroup_test.go +++ b/test/apivalidations/securitygroup_test.go @@ -65,104 +65,41 @@ var _ = Describe("ORC SecurityGroup API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal security group and managementPolicy should default to managed", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec()) - Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) - Expect(securityGroup.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) - }) - - It("should require import for unmanaged", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - - patch.Spec.WithImport(testSecurityGroupImport()) - Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testSecurityGroupImport()). - WithResource(testSecurityGroupResource()) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - }) - - It("should not permit empty import", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.SecurityGroupImport()) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.SecurityGroupImport(). - WithFilter(applyconfigv1alpha1.SecurityGroupFilter())) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - }) - - It("should permit import filter with name", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.SecurityGroupImport(). - WithFilter(applyconfigv1alpha1.SecurityGroupFilter().WithName("foo"))) - Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - - patch.Spec.WithResource(testSecurityGroupResource()) - Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec. - WithImport(testSecurityGroupImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testSecurityGroupResource()) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec. - WithImport(testSecurityGroupImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, securityGroup, patch)).NotTo(Succeed()) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - securityGroup := securityGroupStub(namespace) - patch := baseSecurityGroupPatch(securityGroup) - patch.Spec.WithResource(applyconfigv1alpha1.SecurityGroupResourceSpec()) - patch.Spec. - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)).WithResource( - applyconfigv1alpha1.SecurityGroupResourceSpec()) - Expect(applyObj(ctx, securityGroup, patch)).To(Succeed()) - Expect(securityGroup.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.SecurityGroupApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return securityGroupStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.SecurityGroupApplyConfiguration { + return baseSecurityGroupPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithResource(testSecurityGroupResource()) + }, + applyImport: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithImport(testSecurityGroupImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SecurityGroupImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SecurityGroupImport().WithFilter(applyconfigv1alpha1.SecurityGroupFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SecurityGroupImport().WithFilter(applyconfigv1alpha1.SecurityGroupFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.SecurityGroupApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.SecurityGroup).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.SecurityGroup).Spec.ManagedOptions.OnDelete + }, }) It("should not permit invalid direction", func(ctx context.Context) { diff --git a/test/apivalidations/server_test.go b/test/apivalidations/server_test.go index 789f03b09..058fc8b2b 100644 --- a/test/apivalidations/server_test.go +++ b/test/apivalidations/server_test.go @@ -63,12 +63,41 @@ var _ = Describe("ORC Server API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal server and managementPolicy should default to managed", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec.WithResource(testServerResource()) - Expect(applyObj(ctx, server, patch)).To(Succeed()) - Expect(server.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.ServerApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return serverStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.ServerApplyConfiguration { + return baseServerPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithResource(testServerResource()) + }, + applyImport: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithImport(testServerImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServerImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServerImport().WithFilter(applyconfigv1alpha1.ServerFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServerImport().WithFilter(applyconfigv1alpha1.ServerFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.ServerApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Server).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Server).Spec.ManagedOptions.OnDelete + }, }) It("should reject a server without required fields", func(ctx context.Context) { @@ -192,94 +221,4 @@ var _ = Describe("ORC Server API validations", func() { WithTags("foo", "bar", "foo")) Expect(applyObj(ctx, server, patch)).NotTo(Succeed()) }) - - It("should require import for unmanaged", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testServerImport()) - Expect(applyObj(ctx, server, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testServerImport()). - WithResource(testServerResource()) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServerImport()) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServerImport(). - WithFilter(applyconfigv1alpha1.ServerFilter())) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServerImport(). - WithFilter(applyconfigv1alpha1.ServerFilter().WithName("foo"))) - Expect(applyObj(ctx, server, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testServerResource()) - Expect(applyObj(ctx, server, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec. - WithImport(testServerImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testServerResource()) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec. - WithImport(testServerImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - server := serverStub(namespace) - patch := baseServerPatch(server) - patch.Spec.WithResource(testServerResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, server, patch)).To(Succeed()) - Expect(server.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/servergroup_test.go b/test/apivalidations/servergroup_test.go index f211749d9..1e9252222 100644 --- a/test/apivalidations/servergroup_test.go +++ b/test/apivalidations/servergroup_test.go @@ -61,12 +61,39 @@ var _ = Describe("ORC ServerGroup API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal servergroup and managementPolicy should default to managed", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec.WithResource(testServerGroupResource()) - Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) - Expect(serverGroup.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.ServerGroupApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return serverGroupStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.ServerGroupApplyConfiguration { + return baseServerGroupPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithResource(testServerGroupResource()) + }, + applyImport: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { p.Spec.WithImport(testServerGroupImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServerGroupImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServerGroupImport().WithFilter(applyconfigv1alpha1.ServerGroupFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServerGroupImport().WithFilter(applyconfigv1alpha1.ServerGroupFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.ServerGroupApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.ServerGroup).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.ServerGroup).Spec.ManagedOptions.OnDelete + }, }) It("should reject a servergroup without required field policy", func(ctx context.Context) { @@ -127,94 +154,4 @@ var _ = Describe("ORC ServerGroup API validations", func() { WithRules(applyconfigv1alpha1.ServerGroupRules().WithMaxServerPerHost(2))) Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("maxServerPerHost can only be used with the anti-affinity policy"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testServerGroupImport()) - Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testServerGroupImport()). - WithResource(testServerGroupResource()) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServerGroupImport()) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServerGroupImport(). - WithFilter(applyconfigv1alpha1.ServerGroupFilter())) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServerGroupImport(). - WithFilter(applyconfigv1alpha1.ServerGroupFilter().WithName("foo"))) - Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testServerGroupResource()) - Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec. - WithImport(testServerGroupImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testServerGroupResource()) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec. - WithImport(testServerGroupImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, serverGroup, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - serverGroup := serverGroupStub(namespace) - patch := baseServerGroupPatch(serverGroup) - patch.Spec.WithResource(testServerGroupResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, serverGroup, patch)).To(Succeed()) - Expect(serverGroup.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/service_test.go b/test/apivalidations/service_test.go index 82f57f02d..4c6c0ca1a 100644 --- a/test/apivalidations/service_test.go +++ b/test/apivalidations/service_test.go @@ -60,12 +60,35 @@ var _ = Describe("ORC Service API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal service and managementPolicy should default to managed", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec.WithResource(testServiceResource()) - Expect(applyObj(ctx, service, patch)).To(Succeed()) - Expect(service.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.ServiceApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return serviceStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.ServiceApplyConfiguration { return baseServicePatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { p.Spec.WithResource(testServiceResource()) }, + applyImport: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { p.Spec.WithImport(testServiceImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServiceImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServiceImport().WithFilter(applyconfigv1alpha1.ServiceFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ServiceImport().WithFilter(applyconfigv1alpha1.ServiceFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.ServiceApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Service).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Service).Spec.ManagedOptions.OnDelete + }, }) It("should reject a service without required field type", func(ctx context.Context) { @@ -74,94 +97,4 @@ var _ = Describe("ORC Service API validations", func() { patch.Spec.WithResource(applyconfigv1alpha1.ServiceResourceSpec()) Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("spec.resource.type"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testServiceImport()) - Expect(applyObj(ctx, service, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testServiceImport()). - WithResource(testServiceResource()) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServiceImport()) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServiceImport(). - WithFilter(applyconfigv1alpha1.ServiceFilter())) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.ServiceImport(). - WithFilter(applyconfigv1alpha1.ServiceFilter().WithName("foo"))) - Expect(applyObj(ctx, service, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testServiceResource()) - Expect(applyObj(ctx, service, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec. - WithImport(testServiceImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testServiceResource()) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec. - WithImport(testServiceImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, service, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - service := serviceStub(namespace) - patch := baseServicePatch(service) - patch.Spec.WithResource(testServiceResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, service, patch)).To(Succeed()) - Expect(service.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/subnet_test.go b/test/apivalidations/subnet_test.go index 0177a8852..7915de3c9 100644 --- a/test/apivalidations/subnet_test.go +++ b/test/apivalidations/subnet_test.go @@ -41,6 +41,23 @@ func subnetStub(namespace *corev1.Namespace) *orcv1alpha1.Subnet { return obj } +func testSubnetResource() *applyconfigv1alpha1.SubnetResourceSpecApplyConfiguration { + return applyconfigv1alpha1.SubnetResourceSpec(). + WithNetworkRef(networkName). + WithIPVersion(4). + WithCIDR("192.168.100.0/24") +} + +func testSubnetImport() *applyconfigv1alpha1.SubnetImportApplyConfiguration { + return applyconfigv1alpha1.SubnetImport().WithID(subnetID) +} + +func baseSubnetPatchBase(subnet client.Object) *applyconfigv1alpha1.SubnetApplyConfiguration { + return applyconfigv1alpha1.Subnet(subnet.GetName(), subnet.GetNamespace()). + WithSpec(applyconfigv1alpha1.SubnetSpec(). + WithCloudCredentialsRef(testCredentials())) +} + func baseSubnetPatch(subnet client.Object) *applyconfigv1alpha1.SubnetApplyConfiguration { return applyconfigv1alpha1.Subnet(subnet.GetName(), subnet.GetNamespace()). WithSpec(applyconfigv1alpha1.SubnetSpec(). @@ -57,12 +74,43 @@ var _ = Describe("ORC Subnet API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal subnet and managementPolicy should default to managed", func(ctx context.Context) { - subnet := subnetStub(namespace) - patch := baseSubnetPatch(subnet) - Expect(applyObj(ctx, subnet, patch)).To(Succeed()) - Expect(subnet.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.SubnetApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return subnetStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.SubnetApplyConfiguration { + return baseSubnetPatchBase(obj) + }, + applyResource: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithResource(testSubnetResource()) + }, + applyImport: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithImport(testSubnetImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SubnetImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SubnetImport().WithFilter(applyconfigv1alpha1.SubnetFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.SubnetImport().WithFilter(applyconfigv1alpha1.SubnetFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.SubnetApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Subnet).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Subnet).Spec.ManagedOptions.OnDelete + }, }) + It("should allow valid tags", func(ctx context.Context) { subnet := subnetStub(namespace) patch := baseSubnetPatch(subnet) @@ -203,16 +251,6 @@ var _ = Describe("ORC Subnet API validations", func() { Expect(applyObj(ctx, subnet, patch)).To(Succeed()) }) - It("should not permit empty import filter", func(ctx context.Context) { - subnet := subnetStub(namespace) - patch := baseSubnetPatch(subnet) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.SubnetImport(). - WithFilter(applyconfigv1alpha1.SubnetFilter())) - Expect(applyObj(ctx, subnet, patch)).NotTo(Succeed()) - }) - It("should not permit invalid import filter", func(ctx context.Context) { network := subnetStub(namespace) patch := baseSubnetPatch(network) diff --git a/test/apivalidations/trunk_test.go b/test/apivalidations/trunk_test.go index 506eb4329..fba896ba9 100644 --- a/test/apivalidations/trunk_test.go +++ b/test/apivalidations/trunk_test.go @@ -61,12 +61,41 @@ var _ = Describe("ORC Trunk API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal trunk and managementPolicy should default to managed", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec.WithResource(testTrunkResource()) - Expect(applyObj(ctx, trunk, patch)).To(Succeed()) - Expect(trunk.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.TrunkApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return trunkStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.TrunkApplyConfiguration { + return baseTrunkPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithResource(testTrunkResource()) + }, + applyImport: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithImport(testTrunkImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.TrunkImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.TrunkImport().WithFilter(applyconfigv1alpha1.TrunkFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.TrunkImport().WithFilter(applyconfigv1alpha1.TrunkFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.TrunkApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Trunk).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Trunk).Spec.ManagedOptions.OnDelete + }, }) It("should reject a trunk without required field portRef", func(ctx context.Context) { @@ -148,94 +177,4 @@ var _ = Describe("ORC Trunk API validations", func() { WithSegmentationType("vlan"))) Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.resource.subports[0].segmentationID"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testTrunkImport()) - Expect(applyObj(ctx, trunk, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testTrunkImport()). - WithResource(testTrunkResource()) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.TrunkImport()) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.TrunkImport(). - WithFilter(applyconfigv1alpha1.TrunkFilter())) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.TrunkImport(). - WithFilter(applyconfigv1alpha1.TrunkFilter().WithName("foo"))) - Expect(applyObj(ctx, trunk, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testTrunkResource()) - Expect(applyObj(ctx, trunk, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec. - WithImport(testTrunkImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testTrunkResource()) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec. - WithImport(testTrunkImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, trunk, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - trunk := trunkStub(namespace) - patch := baseTrunkPatch(trunk) - patch.Spec.WithResource(testTrunkResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, trunk, patch)).To(Succeed()) - Expect(trunk.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/user_test.go b/test/apivalidations/user_test.go index 91b4b51e6..cc7024b9e 100644 --- a/test/apivalidations/user_test.go +++ b/test/apivalidations/user_test.go @@ -60,12 +60,35 @@ var _ = Describe("ORC User API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal user and managementPolicy should default to managed", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec.WithResource(testUserResource()) - Expect(applyObj(ctx, user, patch)).To(Succeed()) - Expect(user.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.UserApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return userStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.UserApplyConfiguration { return baseUserPatch(obj) }, + applyResource: func(p *applyconfigv1alpha1.UserApplyConfiguration) { p.Spec.WithResource(testUserResource()) }, + applyImport: func(p *applyconfigv1alpha1.UserApplyConfiguration) { p.Spec.WithImport(testUserImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.UserApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.UserImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.UserApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.UserImport().WithFilter(applyconfigv1alpha1.UserFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.UserApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.UserImport().WithFilter(applyconfigv1alpha1.UserFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.UserApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.UserApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.UserApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.User).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.User).Spec.ManagedOptions.OnDelete + }, }) It("should have immutable domainRef", func(ctx context.Context) { @@ -91,94 +114,4 @@ var _ = Describe("ORC User API validations", func() { WithDefaultProjectRef("project-b")) Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("defaultProjectRef is immutable"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testUserImport()) - Expect(applyObj(ctx, user, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testUserImport()). - WithResource(testUserResource()) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.UserImport()) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.UserImport(). - WithFilter(applyconfigv1alpha1.UserFilter())) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.UserImport(). - WithFilter(applyconfigv1alpha1.UserFilter().WithName("foo"))) - Expect(applyObj(ctx, user, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testUserResource()) - Expect(applyObj(ctx, user, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec. - WithImport(testUserImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testUserResource()) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec. - WithImport(testUserImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - user := userStub(namespace) - patch := baseUserPatch(user) - patch.Spec.WithResource(testUserResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, user, patch)).To(Succeed()) - Expect(user.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/volume_test.go b/test/apivalidations/volume_test.go index 8c7c822e6..6bd5bed00 100644 --- a/test/apivalidations/volume_test.go +++ b/test/apivalidations/volume_test.go @@ -60,12 +60,41 @@ var _ = Describe("ORC Volume API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal volume and managementPolicy should default to managed", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec.WithResource(testVolumeResource()) - Expect(applyObj(ctx, volume, patch)).To(Succeed()) - Expect(volume.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.VolumeApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return volumeStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.VolumeApplyConfiguration { + return baseVolumePatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithResource(testVolumeResource()) + }, + applyImport: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithImport(testVolumeImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.VolumeImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.VolumeImport().WithFilter(applyconfigv1alpha1.VolumeFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.VolumeImport().WithFilter(applyconfigv1alpha1.VolumeFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.VolumeApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.Volume).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.Volume).Spec.ManagedOptions.OnDelete + }, }) It("should reject a volume without required field size", func(ctx context.Context) { @@ -127,94 +156,4 @@ var _ = Describe("ORC Volume API validations", func() { WithSize(1).WithImageRef("image-b")) Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("imageRef is immutable"))) }) - - It("should require import for unmanaged", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testVolumeImport()) - Expect(applyObj(ctx, volume, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testVolumeImport()). - WithResource(testVolumeResource()) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.VolumeImport()) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.VolumeImport(). - WithFilter(applyconfigv1alpha1.VolumeFilter())) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.VolumeImport(). - WithFilter(applyconfigv1alpha1.VolumeFilter().WithName("foo"))) - Expect(applyObj(ctx, volume, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testVolumeResource()) - Expect(applyObj(ctx, volume, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec. - WithImport(testVolumeImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testVolumeResource()) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec. - WithImport(testVolumeImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, volume, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - volume := volumeStub(namespace) - patch := baseVolumePatch(volume) - patch.Spec.WithResource(testVolumeResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, volume, patch)).To(Succeed()) - Expect(volume.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) diff --git a/test/apivalidations/volumetype_test.go b/test/apivalidations/volumetype_test.go index 0ce3d227b..08338b184 100644 --- a/test/apivalidations/volumetype_test.go +++ b/test/apivalidations/volumetype_test.go @@ -60,12 +60,39 @@ var _ = Describe("ORC VolumeType API validations", func() { namespace = createNamespace() }) - It("should allow to create a minimal volumetype and managementPolicy should default to managed", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec.WithResource(testVolumeTypeResource()) - Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) - Expect(volumeType.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.VolumeTypeApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return volumeTypeStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.VolumeTypeApplyConfiguration { + return baseVolumeTypePatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithResource(testVolumeTypeResource()) + }, + applyImport: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { p.Spec.WithImport(testVolumeTypeImport()) }, + applyEmptyImport: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.VolumeTypeImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.VolumeTypeImport().WithFilter(applyconfigv1alpha1.VolumeTypeFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.VolumeTypeImport().WithFilter(applyconfigv1alpha1.VolumeTypeFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.VolumeTypeApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.VolumeType).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.VolumeType).Spec.ManagedOptions.OnDelete + }, }) It("should permit extraSpecs with required fields", func(ctx context.Context) { @@ -76,94 +103,4 @@ var _ = Describe("ORC VolumeType API validations", func() { WithName("key").WithValue("value"))) Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) }) - - It("should require import for unmanaged", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("import must be specified when policy is unmanaged"))) - - patch.Spec.WithImport(testVolumeTypeImport()) - Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) - }) - - It("should not permit unmanaged with resource", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(testVolumeTypeImport()). - WithResource(testVolumeTypeResource()) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("resource may not be specified when policy is unmanaged"))) - }) - - It("should not permit empty import", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.VolumeTypeImport()) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("spec.import in body should have at least 1 properties"))) - }) - - It("should not permit empty import filter", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.VolumeTypeImport(). - WithFilter(applyconfigv1alpha1.VolumeTypeFilter())) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least 1 properties"))) - }) - - It("should permit import filter with name", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec. - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithImport(applyconfigv1alpha1.VolumeTypeImport(). - WithFilter(applyconfigv1alpha1.VolumeTypeFilter().WithName("foo"))) - Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) - }) - - It("should require resource for managed", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("resource must be specified when policy is managed"))) - - patch.Spec.WithResource(testVolumeTypeResource()) - Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) - }) - - It("should not permit managed with import", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec. - WithImport(testVolumeTypeImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged). - WithResource(testVolumeTypeResource()) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("import may not be specified when policy is managed"))) - }) - - It("should not permit managedOptions for unmanaged", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec. - WithImport(testVolumeTypeImport()). - WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, volumeType, patch)).To(MatchError(ContainSubstring("managedOptions may only be provided when policy is managed"))) - }) - - It("should permit managedOptions for managed", func(ctx context.Context) { - volumeType := volumeTypeStub(namespace) - patch := baseVolumeTypePatch(volumeType) - patch.Spec.WithResource(testVolumeTypeResource()). - WithManagedOptions(applyconfigv1alpha1.ManagedOptions(). - WithOnDelete(orcv1alpha1.OnDeleteDetach)) - Expect(applyObj(ctx, volumeType, patch)).To(Succeed()) - Expect(volumeType.Spec.ManagedOptions.OnDelete).To(Equal(orcv1alpha1.OnDelete("detach"))) - }) }) From 19da6b5766df864a7609e7639f097216181c346a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 13 Mar 2026 15:37:23 +0100 Subject: [PATCH 087/121] scaffold-controller: generate API validation tests Add a template that generates test/apivalidations/_test.go when scaffolding a new controller. The generated test file includes management policy tests via the shared runManagementPolicyTests helper, required field validation, and immutability tests for all dependency refs. --- .../apivalidation_test.go.template | 136 ++++++++++++++++++ cmd/scaffold-controller/main.go | 3 + 2 files changed, 139 insertions(+) create mode 100644 cmd/scaffold-controller/data/apivalidation/apivalidation_test.go.template diff --git a/cmd/scaffold-controller/data/apivalidation/apivalidation_test.go.template b/cmd/scaffold-controller/data/apivalidation/apivalidation_test.go.template new file mode 100644 index 000000000..7e565b450 --- /dev/null +++ b/cmd/scaffold-controller/data/apivalidation/apivalidation_test.go.template @@ -0,0 +1,136 @@ +/* +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 ( +{{- if .AllCreateDependencies }} + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +{{- else }} + . "github.com/onsi/ginkgo/v2" +{{- end }} + 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 ( + {{ .PackageName }}Name = "{{ .PackageName }}" + {{ .PackageName }}ID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func {{ .PackageName }}Stub(namespace *corev1.Namespace) *orcv1alpha1.{{ .Kind }} { + obj := &orcv1alpha1.{{ .Kind }}{} + obj.Name = {{ .PackageName }}Name + obj.Namespace = namespace.Name + return obj +} + +func test{{ .Kind }}Resource() *applyconfigv1alpha1.{{ .Kind }}ResourceSpecApplyConfiguration { + return applyconfigv1alpha1.{{ .Kind }}ResourceSpec(){{ range .RequiredCreateDependencies }}. + With{{ . }}Ref("{{ . | lower }}"){{ end }} +} + +func base{{ .Kind }}Patch(obj client.Object) *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration { + return applyconfigv1alpha1.{{ .Kind }}(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.{{ .Kind }}Spec(). + WithCloudCredentialsRef(testCredentials())) +} + +func test{{ .Kind }}Import() *applyconfigv1alpha1.{{ .Kind }}ImportApplyConfiguration { + return applyconfigv1alpha1.{{ .Kind }}Import().WithID({{ .PackageName }}ID) +} + +var _ = Describe("ORC {{ .Kind }} API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return {{ .PackageName }}Stub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration { + return base{{ .Kind }}Patch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithResource(test{{ .Kind }}Resource()) + }, + applyImport: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithImport(test{{ .Kind }}Import()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.{{ .Kind }}Import()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.{{ .Kind }}Import().WithFilter(applyconfigv1alpha1.{{ .Kind }}Filter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.{{ .Kind }}Import().WithFilter(applyconfigv1alpha1.{{ .Kind }}Filter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.{{ .Kind }}ApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.{{ .Kind }}).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.{{ .Kind }}).Spec.ManagedOptions.OnDelete + }, + }) +{{- if .RequiredCreateDependencies }} + + It("should reject a {{ .PackageName }} without required fields", func(ctx context.Context) { + obj := {{ .PackageName }}Stub(namespace) + patch := base{{ .Kind }}Patch(obj) + patch.Spec.WithResource(applyconfigv1alpha1.{{ .Kind }}ResourceSpec()) + Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) + }) +{{- end }} +{{- range .AllCreateDependencies }} + + It("should have immutable {{ . | camelCase }}Ref", func(ctx context.Context) { + obj := {{ $.PackageName }}Stub(namespace) + patch := base{{ $.Kind }}Patch(obj) + patch.Spec.WithResource(test{{ $.Kind }}Resource(). + With{{ . }}Ref("{{ . | lower }}-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(test{{ $.Kind }}Resource(). + With{{ . }}Ref("{{ . | lower }}-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("{{ . | camelCase }}Ref is immutable"))) + }) +{{- end }} + + // 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/cmd/scaffold-controller/main.go b/cmd/scaffold-controller/main.go index 854c2bf21..e26dfe300 100644 --- a/cmd/scaffold-controller/main.go +++ b/cmd/scaffold-controller/main.go @@ -185,6 +185,7 @@ func main() { render("data/controller", filepath.Join("internal", "controllers", fields.PackageName), &fields) render("data/tests", filepath.Join("internal", "controllers", fields.PackageName, "tests"), &fields) render("data/samples", filepath.Join("config", "samples"), &fields) + render("data/apivalidation", filepath.Join("test", "apivalidations"), &fields) } func render(srcDir, distDir string, resource *templateFields) { @@ -231,6 +232,8 @@ func render(srcDir, distDir string, resource *templateFields) { tplName = resource.PackageName + ".go" case "sample.yaml": tplName = "openstack_v1alpha1_" + resource.PackageName + ".yaml" + case "apivalidation_test.go": + tplName = resource.PackageName + "_test.go" } var funcMap = template.FuncMap{ From 250c4d89047d0024ee11f9e35872123a965e7754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 19 Mar 2026 14:46:41 +0100 Subject: [PATCH 088/121] docs: document scaffolded API validation tests Update scaffolding.md to list the generated API validation test file, expand the API validation tests section in writing-tests.md with guidance on what to test and how, and update the new-controller skill with Step 7 instructions and a checklist item for API validation tests. --- .agents/skills/new-controller/SKILL.md | 5 ++++- website/docs/development/scaffolding.md | 6 ++++-- website/docs/development/writing-tests.md | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.agents/skills/new-controller/SKILL.md b/.agents/skills/new-controller/SKILL.md index 352849859..af8ef4be7 100644 --- a/.agents/skills/new-controller/SKILL.md +++ b/.agents/skills/new-controller/SKILL.md @@ -215,7 +215,9 @@ Implement: **This step is required** - do not skip it. -Complete the test stubs in `internal/controllers//tests/` and run tests following @.agents/skills/testing/SKILL.md +Complete the scaffolded API validation test in `test/apivalidations/_test.go` by adding tests for any resource-specific validations (enums, numeric ranges, tag uniqueness, format validation, cross-field rules). Look for `TODO(scaffolding)` markers in the generated file. + +Complete the E2E test stubs in `internal/controllers//tests/` and run tests following @.agents/skills/testing/SKILL.md ## Checklist @@ -241,6 +243,7 @@ Complete the test stubs in `internal/controllers//tests/` and run tests fo - [ ] Status writer implemented - [ ] Update reconciler includes tags update (if tags are mutable) - [ ] All TODOs resolved +- [ ] API validation tests complete (resource-specific validations added to scaffolded test) - [ ] `make generate` runs cleanly - [ ] `make lint` passes - [ ] `make test` passes diff --git a/website/docs/development/scaffolding.md b/website/docs/development/scaffolding.md index 7c7c33bf7..d1417f9d0 100644 --- a/website/docs/development/scaffolding.md +++ b/website/docs/development/scaffolding.md @@ -62,6 +62,7 @@ The scaffolding tool generates the following files: ### Tests +- `test/apivalidations/_test.go` - API validation tests (management policy, immutability) - `internal/controllers//tests/-create-minimal/` - Minimal creation test - `internal/controllers//tests/-create-full/` - Full creation test - `internal/controllers//tests/-import/` - Import test @@ -171,7 +172,7 @@ controllers := []interfaces.Controller{ Search the generated code for `TODO(scaffolding)` markers and implement each one: ```bash -grep -r "TODO(scaffolding)" api/ internal/controllers// +grep -r "TODO(scaffolding)" api/ internal/controllers// test/apivalidations/ ``` Key areas requiring implementation: @@ -179,7 +180,8 @@ Key areas requiring implementation: - [API types](api-design.md): Define `Filter`, `ResourceSpec`, and `ResourceStatus` structs - [Actuator](interfaces.md#actuator): Implement `CreateResource`, `DeleteResource`, and optionally `GetResourceReconcilers` - [Status writer](interfaces.md#resourcestatuswriter): Implement `ResourceAvailableStatus` and `ApplyResourceStatus` -- [Tests](writing-tests.md): Ensure the tests for your controller are complete +- [Tests](writing-tests.md): Ensure the tests for your controller are complete, including + [API validation tests](writing-tests.md#api-validation-tests) for any resource-specific validations !!! note diff --git a/website/docs/development/writing-tests.md b/website/docs/development/writing-tests.md index 0beeca6d8..e01b8bf8f 100644 --- a/website/docs/development/writing-tests.md +++ b/website/docs/development/writing-tests.md @@ -14,8 +14,11 @@ fields, you should add tests for those fields. All APIs are expected to have good API validation test coverage. API validation tests ensure that any validations defined in the -API and included in the CRD perform as expected. Add API validation tests for -your controller in `test/apivalidations`. +API and included in the CRD perform as expected. They run against a real +Kubernetes API server (via envtest) using Server-Side Apply, exercising the CEL +rules and OpenAPI schema validations baked into the CRDs. + +Add API validation tests for your controller in `test/apivalidations/`. ### Controller-specific tests From 3db8ec2790ff49fcb92776d5b19a0133b2078534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 20 Mar 2026 15:14:36 +0100 Subject: [PATCH 089/121] Bump google.golang.org/grpc Fixes CVE-2026-33186. --- go.mod | 12 ++++++------ go.sum | 26 ++++++++++++++------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 99ba5c934..977da4ba3 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -86,7 +86,7 @@ require ( golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.50.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect @@ -94,10 +94,10 @@ require ( golang.org/x/tools v0.42.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 0655f923b..a3a20c47d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -215,8 +215,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -251,14 +251,16 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 7d4d79f9454746fe496023dda1c0b65c90ff16ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:35:48 +0000 Subject: [PATCH 090/121] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 4 updates Bumps the all-go-mod-patch-and-minor group with 3 updates in the / directory: [k8s.io/api](https://github.com/kubernetes/api), [k8s.io/client-go](https://github.com/kubernetes/client-go) and [k8s.io/code-generator](https://github.com/kubernetes/code-generator). Updates `k8s.io/api` from 0.34.5 to 0.34.6 - [Commits](https://github.com/kubernetes/api/compare/v0.34.5...v0.34.6) Updates `k8s.io/apimachinery` from 0.34.5 to 0.34.6 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.5...v0.34.6) Updates `k8s.io/client-go` from 0.34.5 to 0.34.6 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.5...v0.34.6) Updates `k8s.io/code-generator` from 0.34.5 to 0.34.6 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.5...v0.34.6) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.34.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/apimachinery dependency-version: 0.34.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/client-go dependency-version: 0.34.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/code-generator dependency-version: 0.34.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-go-mod-patch-and-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 977da4ba3..cd0606913 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 golang.org/x/text v0.35.0 - k8s.io/api v0.34.5 - k8s.io/apimachinery v0.34.5 - k8s.io/client-go v0.34.5 - k8s.io/code-generator v0.34.5 + k8s.io/api v0.34.6 + k8s.io/apimachinery v0.34.6 + k8s.io/client-go v0.34.6 + k8s.io/code-generator v0.34.6 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 diff --git a/go.sum b/go.sum index a3a20c47d..e15e232b0 100644 --- a/go.sum +++ b/go.sum @@ -271,18 +271,18 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.5 h1:+cFkROLIixuQqUZhxizqJKfoT4iwAJneG7NQwqWYyIU= -k8s.io/api v0.34.5/go.mod h1:0RmYc0hpIHEA5s7AyzcPp6j62Z0tRZ+Y7mFFZeXPBuI= +k8s.io/api v0.34.6 h1:0ReeOHQfV9SwQ8CMOHkPbM/GscIT3gN2qh463TOEEk4= +k8s.io/api v0.34.6/go.mod h1:u6eOg5ckbO2DUKiyVp7mUMVIA+qZZdW2oyKDhs8nXec= k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.5 h1:vXJoeBDaW4D9mayqjP1CrKH8kHyucNRvaLjDJaJOc08= -k8s.io/apimachinery v0.34.5/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.6 h1:Y/ZNX0Mf1E+CT8clgFzLIkOhkbRLTSHqv6+eJnMJaoQ= +k8s.io/apimachinery v0.34.6/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= -k8s.io/client-go v0.34.5 h1:eZiO7gq+FfrB8hR7/Z5erA+QEbShtp4DMgJdboEzwhY= -k8s.io/client-go v0.34.5/go.mod h1:olcW68aK21BJeIWNXrreRNeZJJfyIbxJ98FYNN/WC5Y= -k8s.io/code-generator v0.34.5 h1:l27oe2+u0RK2PlJJZniGxRR+bog0Gu33murw3XdGGck= -k8s.io/code-generator v0.34.5/go.mod h1:KZMWjn69ikiAVbCK6fywYeZulk+lSLHB68ILEC2pzhc= +k8s.io/client-go v0.34.6 h1:8aF4tJiZolSdliT5nhJnBx49Om2ET3Tn3/JKKpJk4gI= +k8s.io/client-go v0.34.6/go.mod h1:ZntANq4HsaiOD0rIhLHTdZT/aLkv4NVyI/glqocESTQ= +k8s.io/code-generator v0.34.6 h1:Sff8VcHxpVj/1tvYE7CgL1X7/hHjH2Nnu2//BNVUAaY= +k8s.io/code-generator v0.34.6/go.mod h1:21o4G2tzuXrgpwntJeWA5Nt+H9ADnXvoQ2fwKlfGkfE= k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= From 045005304430425b6b15468388db7b3ec3e46abc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:36:22 +0000 Subject: [PATCH 091/121] :seedling:(deps): Bump the all-github-actions group with 4 updates Bumps the all-github-actions group with 4 updates: [getsentry/action-github-app-token](https://github.com/getsentry/action-github-app-token), [kiegroup/git-backporting](https://github.com/kiegroup/git-backporting), [actions/cache](https://github.com/actions/cache) and [EndBug/add-and-commit](https://github.com/endbug/add-and-commit). Updates `getsentry/action-github-app-token` from a0061014b82a6a5d6aeeb3b824aced47e3c3a7ef to 5c1e90706fe007857338ac1bfbd7a4177db2f789 - [Release notes](https://github.com/getsentry/action-github-app-token/releases) - [Commits](https://github.com/getsentry/action-github-app-token/compare/a0061014b82a6a5d6aeeb3b824aced47e3c3a7ef...5c1e90706fe007857338ac1bfbd7a4177db2f789) Updates `kiegroup/git-backporting` from 4.8.7 to 4.9.0 - [Release notes](https://github.com/kiegroup/git-backporting/releases) - [Changelog](https://github.com/kiegroup/git-backporting/blob/main/CHANGELOG.md) - [Commits](https://github.com/kiegroup/git-backporting/compare/baae3fe1e3c71bc6b1a2699b3bc1e153a19d5ac7...82e45d73f8d39bc3d7eb4b41859d313696c93ed9) Updates `actions/cache` from 5.0.3 to 5.0.4 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/cdf6c1fa76f9f475f3d7449005a359c84ca0f306...668228422ae6a00e4ad889ee87cd7109ec5666a7) Updates `EndBug/add-and-commit` from 9.1.4 to 10.0.0 - [Release notes](https://github.com/endbug/add-and-commit/releases) - [Commits](https://github.com/endbug/add-and-commit/compare/a94899bca583c204427a224a7af87c02f9b325d5...290ea2c423ad77ca9c62ae0f5b224379612c0321) --- updated-dependencies: - dependency-name: getsentry/action-github-app-token dependency-version: 5c1e90706fe007857338ac1bfbd7a4177db2f789 dependency-type: direct:production dependency-group: all-github-actions - dependency-name: kiegroup/git-backporting dependency-version: 4.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions - dependency-name: actions/cache dependency-version: 5.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions - dependency-name: EndBug/add-and-commit dependency-version: 10.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/backport.yaml | 4 ++-- .github/workflows/pr-dependabot.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index ddd61d4fb..2e5388774 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -28,7 +28,7 @@ jobs: steps: - name: Generate a token from the orc-backport-bot github-app id: generate_token - uses: getsentry/action-github-app-token@a0061014b82a6a5d6aeeb3b824aced47e3c3a7ef + uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 with: app_id: ${{ secrets.BACKPORT_APP_ID }} private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} @@ -37,7 +37,7 @@ jobs: if: > contains(github.event.pull_request.labels.*.name, 'semver:patch') || contains(github.event.label.name, 'semver:patch') - uses: kiegroup/git-backporting@baae3fe1e3c71bc6b1a2699b3bc1e153a19d5ac7 + uses: kiegroup/git-backporting@82e45d73f8d39bc3d7eb4b41859d313696c93ed9 with: target-branch: release-1.0 pull-request: ${{ github.event.pull_request.url }} diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index de3a1a98d..92e80495c 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -27,7 +27,7 @@ jobs: uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 with: go-version: ${{ steps.vars.outputs.go_version }} - - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # tag=v5.0.3 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # tag=v5.0.4 name: Restore go cache with: path: | @@ -40,7 +40,7 @@ jobs: run: make modules - name: Update generated code run: make generate - - uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # tag=v9.1.4 + - uses: EndBug/add-and-commit@290ea2c423ad77ca9c62ae0f5b224379612c0321 # tag=v10.0.0 name: Commit changes with: author_name: dependabot[bot] From 8512f221c960275a6a8485369675bf05d8459b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 16:45:36 +0100 Subject: [PATCH 092/121] Bump kuttl to v0.25.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 32fc80610..f6b7c0465 100644 --- a/Makefile +++ b/Makefile @@ -316,7 +316,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= release-0.22 GOLANGCI_LINT_VERSION ?= v2.7.2 MOCKGEN_VERSION ?= v0.6.0 -KUTTL_VERSION ?= v0.24.0 +KUTTL_VERSION ?= v0.25.0 GOVULNCHECK_VERSION ?= v1.1.4 OPERATOR_SDK_VERSION ?= v1.41.1 From 58f17106e56418df7930253a3879d60df8d4b395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:35:54 +0100 Subject: [PATCH 093/121] Bump golangci-lint to v2.11.4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f6b7c0465..dc53fb24e 100644 --- a/Makefile +++ b/Makefile @@ -314,7 +314,7 @@ OPERATOR_SDK = $(LOCALBIN)/operator-sdk KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= release-0.22 -GOLANGCI_LINT_VERSION ?= v2.7.2 +GOLANGCI_LINT_VERSION ?= v2.11.4 MOCKGEN_VERSION ?= v0.6.0 KUTTL_VERSION ?= v0.25.0 GOVULNCHECK_VERSION ?= v1.1.4 From a9e77666ab9446c385e279a5d495eda5f1184f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:36:16 +0100 Subject: [PATCH 094/121] Fix prealloc lint issues from golangci-lint v2.11.4 --- internal/controllers/endpoint/actuator.go | 9 +++++---- internal/controllers/keypair/actuator.go | 9 +++++---- internal/controllers/servergroup/actuator.go | 8 +++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/controllers/endpoint/actuator.go b/internal/controllers/endpoint/actuator.go index d22213154..71988f403 100644 --- a/internal/controllers/endpoint/actuator.go +++ b/internal/controllers/endpoint/actuator.go @@ -82,10 +82,11 @@ func (actuator endpointActuator) ListOSResourcesForAdoption(ctx context.Context, return nil, false } - var filters []osclients.ResourceFilter[osResourceT] - filters = append(filters, func(e *endpoints.Endpoint) bool { - return e.URL == resourceSpec.URL - }) + filters := []osclients.ResourceFilter[osResourceT]{ + func(e *endpoints.Endpoint) bool { + return e.URL == resourceSpec.URL + }, + } listOpts := endpoints.ListOpts{ Availability: gophercloud.Availability(resourceSpec.Interface), diff --git a/internal/controllers/keypair/actuator.go b/internal/controllers/keypair/actuator.go index d5ecc34f5..b11d361ab 100644 --- a/internal/controllers/keypair/actuator.go +++ b/internal/controllers/keypair/actuator.go @@ -78,10 +78,11 @@ func (actuator keypairActuator) ListOSResourcesForAdoption(ctx context.Context, // Filter by the expected resource name to avoid adopting wrong keypairs. // The OpenStack Keypairs API does not support server-side filtering by name, // so we must use client-side filtering. - var filters []osclients.ResourceFilter[osResourceT] - filters = append(filters, func(kp *keypairs.KeyPair) bool { - return kp.Name == getResourceName(orcObject) - }) + filters := []osclients.ResourceFilter[osResourceT]{ + func(kp *keypairs.KeyPair) bool { + return kp.Name == getResourceName(orcObject) + }, + } return actuator.listOSResources(ctx, filters, keypairs.ListOpts{}), true } diff --git a/internal/controllers/servergroup/actuator.go b/internal/controllers/servergroup/actuator.go index 78ab5499f..bcb41b2d7 100644 --- a/internal/controllers/servergroup/actuator.go +++ b/internal/controllers/servergroup/actuator.go @@ -72,15 +72,13 @@ func (actuator servergroupActuator) ListOSResourcesForAdoption(ctx context.Conte return nil, false } - var filters []osclients.ResourceFilter[osResourceT] - listOpts := servergroups.ListOpts{} - - filters = append(filters, + filters := []osclients.ResourceFilter[osResourceT]{ func(f *servergroups.ServerGroup) bool { name := getResourceName(orcObject) return f.Name == name }, - ) + } + listOpts := servergroups.ListOpts{} return actuator.listOSResources(ctx, filters, &listOpts), true } From 662dab42d55469c04032e404f4c2b9c5795a5e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:59:40 +0100 Subject: [PATCH 095/121] Bump KAL --- tools/orc-api-linter/go.mod | 2 +- tools/orc-api-linter/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/orc-api-linter/go.mod b/tools/orc-api-linter/go.mod index 06dc79143..6151a1f16 100644 --- a/tools/orc-api-linter/go.mod +++ b/tools/orc-api-linter/go.mod @@ -4,7 +4,7 @@ go 1.24.0 require ( golang.org/x/tools v0.41.0 - sigs.k8s.io/kube-api-linter v0.0.0-20260205134631-d65d24a9df89 + sigs.k8s.io/kube-api-linter v0.0.0-20260320123815-c9b9b51b278a ) require ( diff --git a/tools/orc-api-linter/go.sum b/tools/orc-api-linter/go.sum index fce1ec281..28add0ec5 100644 --- a/tools/orc-api-linter/go.sum +++ b/tools/orc-api-linter/go.sum @@ -37,7 +37,7 @@ k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYo k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/kube-api-linter v0.0.0-20260205134631-d65d24a9df89 h1:QuWBEzbBkQyuwWPKDEaUBGr8QdHilkc4CdJYCeU1SIo= -sigs.k8s.io/kube-api-linter v0.0.0-20260205134631-d65d24a9df89/go.mod h1:5mP60UakkCye+eOcZ5p98VnV2O49qreW1gq9TdsUf7Q= +sigs.k8s.io/kube-api-linter v0.0.0-20260320123815-c9b9b51b278a h1:36He06lekH8jv21Z88RiGHRswh/cBoXKfSbFleF7ukM= +sigs.k8s.io/kube-api-linter v0.0.0-20260320123815-c9b9b51b278a/go.mod h1:5mP60UakkCye+eOcZ5p98VnV2O49qreW1gq9TdsUf7Q= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 04da31997e88f363e7f979014427aed23639554b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:51:38 +0100 Subject: [PATCH 096/121] Bump trivy to v0.69.3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dc53fb24e..15906748f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ IMG ?= controller:latest BUNDLE_IMG ?= bundle:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.29.0 -TRIVY_VERSION = 0.49.1 +TRIVY_VERSION = 0.69.3 GO_VERSION ?= 1.25.8 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) From 360582237f5fa9d5d11fd8638daad3c9bd54070d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:40:03 +0100 Subject: [PATCH 097/121] Bump kustomize to v5.8.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 15906748f..a2d24c91f 100644 --- a/Makefile +++ b/Makefile @@ -311,7 +311,7 @@ GOVULNCHECK = $(LOCALBIN)/govulncheck OPERATOR_SDK = $(LOCALBIN)/operator-sdk ## Tool Versions -KUSTOMIZE_VERSION ?= v5.6.0 +KUSTOMIZE_VERSION ?= v5.8.1 CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= release-0.22 GOLANGCI_LINT_VERSION ?= v2.11.4 From f49376ea7943e844733b39a57117409dcfd8d63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:40:08 +0100 Subject: [PATCH 098/121] Bump operator-sdk to v1.42.2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a2d24c91f..833535883 100644 --- a/Makefile +++ b/Makefile @@ -318,7 +318,7 @@ GOLANGCI_LINT_VERSION ?= v2.11.4 MOCKGEN_VERSION ?= v0.6.0 KUTTL_VERSION ?= v0.25.0 GOVULNCHECK_VERSION ?= v1.1.4 -OPERATOR_SDK_VERSION ?= v1.41.1 +OPERATOR_SDK_VERSION ?= v1.42.2 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. From ac3e403a6f098bcd832d406e2cd32f014024c696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 18:06:03 +0100 Subject: [PATCH 099/121] Bump controller-tools to v0.20.1 and envtest to release-0.23 --- Makefile | 4 ++-- config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_domains.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_endpoints.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_flavors.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_floatingips.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_groups.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_images.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_keypairs.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_networks.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_ports.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_projects.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_roles.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_routers.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_servergroups.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_servers.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_services.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_subnets.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_trunks.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_users.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_volumes.yaml | 2 +- config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 833535883..f2e20f11a 100644 --- a/Makefile +++ b/Makefile @@ -312,8 +312,8 @@ OPERATOR_SDK = $(LOCALBIN)/operator-sdk ## Tool Versions KUSTOMIZE_VERSION ?= v5.8.1 -CONTROLLER_TOOLS_VERSION ?= v0.17.1 -ENVTEST_VERSION ?= release-0.22 +CONTROLLER_TOOLS_VERSION ?= v0.20.1 +ENVTEST_VERSION ?= release-0.23 GOLANGCI_LINT_VERSION ?= v2.11.4 MOCKGEN_VERSION ?= v0.6.0 KUTTL_VERSION ?= v0.25.0 diff --git a/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml b/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml index 11fd4e11f..a63c83ef2 100644 --- a/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: addressscopes.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_domains.yaml b/config/crd/bases/openstack.k-orc.cloud_domains.yaml index a1870a5a5..9c9fc2c82 100644 --- a/config/crd/bases/openstack.k-orc.cloud_domains.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_domains.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: domains.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml index 772b0e8b9..efddd5c20 100644 --- a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: endpoints.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml index 833f1ea47..e2e9c08c6 100644 --- a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: flavors.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml index 383366e59..51574f4f1 100644 --- a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: floatingips.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_groups.yaml b/config/crd/bases/openstack.k-orc.cloud_groups.yaml index d29e2161a..9418c3ded 100644 --- a/config/crd/bases/openstack.k-orc.cloud_groups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_groups.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: groups.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_images.yaml b/config/crd/bases/openstack.k-orc.cloud_images.yaml index ff0f543a8..39cfc79ad 100644 --- a/config/crd/bases/openstack.k-orc.cloud_images.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_images.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: images.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml index df1f02cab..c02878ff7 100644 --- a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: keypairs.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_networks.yaml b/config/crd/bases/openstack.k-orc.cloud_networks.yaml index 63951bb0b..ac5f02b8b 100644 --- a/config/crd/bases/openstack.k-orc.cloud_networks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_networks.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: networks.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index 7ea7190a9..e409abdaa 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: ports.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_projects.yaml b/config/crd/bases/openstack.k-orc.cloud_projects.yaml index 6c6804d01..4cc284afc 100644 --- a/config/crd/bases/openstack.k-orc.cloud_projects.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_projects.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: projects.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_roles.yaml b/config/crd/bases/openstack.k-orc.cloud_roles.yaml index 2635b7063..4ec04bfa5 100644 --- a/config/crd/bases/openstack.k-orc.cloud_roles.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_roles.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: roles.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml b/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml index 2770876c0..bbb4187b8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: routerinterfaces.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_routers.yaml b/config/crd/bases/openstack.k-orc.cloud_routers.yaml index dea6ac3a8..7dade737e 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: routers.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml index bec05f320..19eb7d8f9 100644 --- a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: securitygroups.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml index c44d85f12..c0bdb3ced 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: servergroups.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index 8387dd81c..be2b2c9bd 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: servers.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_services.yaml b/config/crd/bases/openstack.k-orc.cloud_services.yaml index 0c113a367..8c5f96c75 100644 --- a/config/crd/bases/openstack.k-orc.cloud_services.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_services.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: services.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml index a37539475..5ff2ede1a 100644 --- a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: subnets.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml index 536db8166..aefa17223 100644 --- a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: trunks.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml index bc8835301..2b7480257 100644 --- a/config/crd/bases/openstack.k-orc.cloud_users.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: users.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml index 6a9371f5c..500dec639 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: volumes.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud diff --git a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml index 20f821828..384a5f215 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 + controller-gen.kubebuilder.io/version: v0.20.1 name: volumetypes.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud From 67ad84fb5832b1adced0ddc760a209d422e1bdff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 18:04:22 +0100 Subject: [PATCH 100/121] Bump crd-ref-docs to v0.3.0 --- website/Makefile | 2 +- website/docs/crd-reference.md | 1724 ++++++++++++++++----------------- 2 files changed, 863 insertions(+), 863 deletions(-) diff --git a/website/Makefile b/website/Makefile index 2c86a223c..9e59454e0 100644 --- a/website/Makefile +++ b/website/Makefile @@ -1,7 +1,7 @@ .PHONY: default default: generated -CRD_REF_DOCS?=github.com/elastic/crd-ref-docs@v0.2.0 +CRD_REF_DOCS?=github.com/elastic/crd-ref-docs@v0.3.0 GOMARKDOC?=github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0 websitedir := $(dir $(lastword $(MAKEFILE_LIST))) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 2513429b8..8d22de9fc 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -49,8 +49,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `ip` _[IPvAny](#ipvany)_ | ip contains a fixed IP address assigned to the port. It must belong
to the referenced subnet's CIDR. If not specified, OpenStack
allocates an available IP from the referenced subnet. | | MaxLength: 45
MinLength: 1
| -| `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef references the subnet from which to allocate the IP
address. | | MaxLength: 253
MinLength: 1
| +| `ip` _[IPvAny](#ipvany)_ | ip contains a fixed IP address assigned to the port. It must belong
to the referenced subnet's CIDR. If not specified, OpenStack
allocates an available IP from the referenced subnet. | | MaxLength: 45
MinLength: 1
Optional: \{\}
| +| `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef references the subnet from which to allocate the IP
address. | | MaxLength: 253
MinLength: 1
Required: \{\}
| #### AddressScope @@ -67,9 +67,9 @@ AddressScope is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `AddressScope` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[AddressScopeSpec](#addressscopespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[AddressScopeStatus](#addressscopestatus)_ | status defines the observed state of the resource. | | | +| `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` _[AddressScopeSpec](#addressscopespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[AddressScopeStatus](#addressscopestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### AddressScopeFilter @@ -86,10 +86,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. | | Enum: [4 6]
| -| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. | | | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. | | Enum: [4 6]
Optional: \{\}
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. | | Optional: \{\}
| #### AddressScopeImport @@ -108,8 +108,8 @@ _Appears in:_ | 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
| -| `filter` _[AddressScopeFilter](#addressscopefilter)_ | 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
| +| `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` _[AddressScopeFilter](#addressscopefilter)_ | 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: \{\}
| #### AddressScopeResourceSpec @@ -125,10 +125,10 @@ _Appears in:_ | 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: `^[^,]+$`
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. | | Enum: [4 6]
| -| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. We can't unshared a shared address scope; Neutron
enforces this. | | | +| `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: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP protocol version. | | Enum: [4 6]
Required: \{\}
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. We can't unshared a shared address scope; Neutron
enforces this. | | Optional: \{\}
| #### AddressScopeResourceStatus @@ -144,10 +144,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
| -| `ipVersion` _integer_ | ipVersion is the IP protocol version. | | | -| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. | | | +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `ipVersion` _integer_ | ipVersion is the IP protocol version. | | Optional: \{\}
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects or not. By default, only admin users can change set
this value. | | Optional: \{\}
| #### AddressScopeSpec @@ -163,11 +163,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[AddressScopeImport](#addressscopeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[AddressScopeResourceSpec](#addressscoperesourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[AddressScopeImport](#addressscopeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[AddressScopeResourceSpec](#addressscoperesourcespec)_ | 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: \{\}
| #### AddressScopeStatus @@ -183,9 +183,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[AddressScopeResourceStatus](#addressscoperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[AddressScopeResourceStatus](#addressscoperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### AllocationPool @@ -201,8 +201,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `start` _[IPvAny](#ipvany)_ | start is the first IP address in the allocation pool. | | MaxLength: 45
MinLength: 1
| -| `end` _[IPvAny](#ipvany)_ | end is the last IP address in the allocation pool. | | MaxLength: 45
MinLength: 1
| +| `start` _[IPvAny](#ipvany)_ | start is the first IP address in the allocation pool. | | MaxLength: 45
MinLength: 1
Required: \{\}
| +| `end` _[IPvAny](#ipvany)_ | end is the last IP address in the allocation pool. | | MaxLength: 45
MinLength: 1
Required: \{\}
| #### AllocationPoolStatus @@ -218,8 +218,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `start` _string_ | start is the first IP address in the allocation pool. | | MaxLength: 1024
| -| `end` _string_ | end is the last IP address in the allocation pool. | | MaxLength: 1024
| +| `start` _string_ | start is the first IP address in the allocation pool. | | MaxLength: 1024
Optional: \{\}
| +| `end` _string_ | end is the last IP address in the allocation pool. | | MaxLength: 1024
Optional: \{\}
| #### AllowedAddressPair @@ -235,8 +235,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `ip` _[IPvAny](#ipvany)_ | ip contains an IP address which a server connected to the port can
send packets with. It can be an IP Address or a CIDR (if supported
by the underlying extension plugin). | | MaxLength: 45
MinLength: 1
| -| `mac` _[MAC](#mac)_ | mac contains a MAC address which a server connected to the port can
send packets with. Defaults to the MAC address of the port. | | MaxLength: 17
MinLength: 1
| +| `ip` _[IPvAny](#ipvany)_ | ip contains an IP address which a server connected to the port can
send packets with. It can be an IP Address or a CIDR (if supported
by the underlying extension plugin). | | MaxLength: 45
MinLength: 1
Required: \{\}
| +| `mac` _[MAC](#mac)_ | mac contains a MAC address which a server connected to the port can
send packets with. Defaults to the MAC address of the port. | | MaxLength: 17
MinLength: 1
Optional: \{\}
| #### AllowedAddressPairStatus @@ -252,8 +252,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `ip` _string_ | ip contains an IP address which a server connected to the port can
send packets with. | | MaxLength: 1024
| -| `mac` _string_ | mac contains a MAC address which a server connected to the port can
send packets with. | | MaxLength: 1024
| +| `ip` _string_ | ip contains an IP address which a server connected to the port can
send packets with. | | MaxLength: 1024
Optional: \{\}
| +| `mac` _string_ | mac contains a MAC address which a server connected to the port can
send packets with. | | MaxLength: 1024
Optional: \{\}
| #### AvailabilityZoneHint @@ -327,8 +327,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `secretName` _string_ | 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
| -| `cloudName` _string_ | cloudName specifies the name of the entry in the clouds.yaml file to use. | | MaxLength: 256
MinLength: 1
| +| `secretName` _string_ | 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
Required: \{\}
| +| `cloudName` _string_ | cloudName specifies the name of the entry in the clouds.yaml file to use. | | MaxLength: 256
MinLength: 1
Required: \{\}
| #### DNSDomain @@ -361,9 +361,9 @@ Domain is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Domain` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[DomainSpec](#domainspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[DomainStatus](#domainstatus)_ | status defines the observed state of the resource. | | | +| `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` _[DomainSpec](#domainspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[DomainStatus](#domainstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### DomainFilter @@ -380,8 +380,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
| -| `enabled` _boolean_ | enabled defines whether a domain is enabled or not. Default is true.
Note: Users can only authorize against an enabled domain (and any of its projects). | | | +| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `enabled` _boolean_ | enabled defines whether a domain is enabled or not. Default is true.
Note: Users can only authorize against an enabled domain (and any of its projects). | | Optional: \{\}
| #### DomainImport @@ -400,8 +400,8 @@ _Appears in:_ | 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
| -| `filter` _[DomainFilter](#domainfilter)_ | 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
| +| `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` _[DomainFilter](#domainfilter)_ | 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: \{\}
| #### DomainResourceSpec @@ -417,9 +417,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `enabled` _boolean_ | enabled defines whether a domain is enabled or not. Default is true.
Note: Users can only authorize against an enabled domain (and any of its projects). | | | +| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `enabled` _boolean_ | enabled defines whether a domain is enabled or not. Default is true.
Note: Users can only authorize against an enabled domain (and any of its projects). | | Optional: \{\}
| #### DomainResourceStatus @@ -435,9 +435,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `enabled` _boolean_ | enabled defines whether a domain is enabled or not. Default is true.
Note: Users can only authorize against an enabled domain (and any of its projects). | | | +| `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: \{\}
| +| `enabled` _boolean_ | enabled defines whether a domain is enabled or not. Default is true.
Note: Users can only authorize against an enabled domain (and any of its projects). | | Optional: \{\}
| #### DomainSpec @@ -453,11 +453,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[DomainImport](#domainimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[DomainResourceSpec](#domainresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[DomainImport](#domainimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[DomainResourceSpec](#domainresourcespec)_ | 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: \{\}
| #### DomainStatus @@ -473,9 +473,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[DomainResourceStatus](#domainresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[DomainResourceStatus](#domainresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Endpoint @@ -492,9 +492,9 @@ Endpoint is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Endpoint` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[EndpointSpec](#endpointspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[EndpointStatus](#endpointstatus)_ | status defines the observed state of the resource. | | | +| `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` _[EndpointSpec](#endpointspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[EndpointStatus](#endpointstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### EndpointFilter @@ -511,9 +511,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `interface` _string_ | interface of the existing endpoint. | | Enum: [admin internal public]
| -| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `url` _string_ | url is the URL of the existing endpoint. | | MaxLength: 1024
| +| `interface` _string_ | interface of the existing endpoint. | | Enum: [admin internal public]
Optional: \{\}
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `url` _string_ | url is the URL of the existing endpoint. | | MaxLength: 1024
Optional: \{\}
| #### EndpointImport @@ -532,8 +532,8 @@ _Appears in:_ | 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
| -| `filter` _[EndpointFilter](#endpointfilter)_ | 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
| +| `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` _[EndpointFilter](#endpointfilter)_ | 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: \{\}
| #### EndpointResourceSpec @@ -549,11 +549,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `enabled` _boolean_ | enabled indicates whether the endpoint is enabled or not. | | | -| `interface` _string_ | interface indicates the visibility of the endpoint. | | Enum: [admin internal public]
| -| `url` _string_ | url is the endpoint URL. | | MaxLength: 1024
| -| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `enabled` _boolean_ | enabled indicates whether the endpoint is enabled or not. | | Optional: \{\}
| +| `interface` _string_ | interface indicates the visibility of the endpoint. | | Enum: [admin internal public]
Required: \{\}
| +| `url` _string_ | url is the endpoint URL. | | MaxLength: 1024
Required: \{\}
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef is a reference to the ORC Service which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| #### EndpointResourceStatus @@ -569,11 +569,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `enabled` _boolean_ | enabled indicates whether the endpoint is enabled or not. | | | -| `interface` _string_ | interface indicates the visibility of the endpoint. | | MaxLength: 128
| -| `url` _string_ | url is the endpoint URL. | | MaxLength: 1024
| -| `serviceID` _string_ | serviceID is the ID of the Service to which the resource is associated. | | MaxLength: 1024
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `enabled` _boolean_ | enabled indicates whether the endpoint is enabled or not. | | Optional: \{\}
| +| `interface` _string_ | interface indicates the visibility of the endpoint. | | MaxLength: 128
Optional: \{\}
| +| `url` _string_ | url is the endpoint URL. | | MaxLength: 1024
Optional: \{\}
| +| `serviceID` _string_ | serviceID is the ID of the Service to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| #### EndpointSpec @@ -589,11 +589,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[EndpointImport](#endpointimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[EndpointResourceSpec](#endpointresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[EndpointImport](#endpointimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[EndpointResourceSpec](#endpointresourcespec)_ | 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: \{\}
| #### EndpointStatus @@ -609,9 +609,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[EndpointResourceStatus](#endpointresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[EndpointResourceStatus](#endpointresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Ethertype @@ -645,7 +645,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which the external
gateway is on. | | MaxLength: 253
MinLength: 1
| +| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which the external
gateway is on. | | MaxLength: 253
MinLength: 1
Required: \{\}
| #### ExternalGatewayStatus @@ -661,7 +661,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `networkID` _string_ | networkID is the ID of the network the gateway is on. | | MaxLength: 1024
| +| `networkID` _string_ | networkID is the ID of the network the gateway is on. | | MaxLength: 1024
Optional: \{\}
| #### FilterByKeystoneTags @@ -677,10 +677,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `tags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| -| `tagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| -| `notTags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| -| `notTagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| +| `tags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `tagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `notTags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `notTagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| #### FilterByNeutronTags @@ -702,10 +702,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `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
| -| `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
| -| `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
| -| `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
| +| `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: \{\}
| #### FilterByServerTags @@ -721,10 +721,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `tags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| -| `tagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| -| `notTags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| -| `notTagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| +| `tags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `tagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `notTags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `notTagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| #### FixedIPStatus @@ -740,8 +740,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `ip` _string_ | ip contains a fixed IP address assigned to the port. | | MaxLength: 1024
| -| `subnetID` _string_ | subnetID is the ID of the subnet this IP is allocated from. | | MaxLength: 1024
| +| `ip` _string_ | ip contains a fixed IP address assigned to the port. | | MaxLength: 1024
Optional: \{\}
| +| `subnetID` _string_ | subnetID is the ID of the subnet this IP is allocated from. | | MaxLength: 1024
Optional: \{\}
| #### Flavor @@ -758,9 +758,9 @@ Flavor is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Flavor` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[FlavorSpec](#flavorspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[FlavorStatus](#flavorstatus)_ | status defines the observed state of the resource. | | | +| `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` _[FlavorSpec](#flavorspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[FlavorStatus](#flavorstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### FlavorFilter @@ -777,10 +777,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `ram` _integer_ | ram is the memory of the flavor, measured in MB. | | Minimum: 1
| -| `vcpus` _integer_ | vcpus is the number of vcpus for the flavor. | | Minimum: 1
| -| `disk` _integer_ | disk is the size of the root disk in GiB. | | Minimum: 0
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `ram` _integer_ | ram is the memory of the flavor, measured in MB. | | Minimum: 1
Optional: \{\}
| +| `vcpus` _integer_ | vcpus is the number of vcpus for the flavor. | | Minimum: 1
Optional: \{\}
| +| `disk` _integer_ | disk is the size of the root disk in GiB. | | Minimum: 0
Optional: \{\}
| #### FlavorImport @@ -799,8 +799,8 @@ _Appears in:_ | 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
| -| `filter` _[FlavorFilter](#flavorfilter)_ | 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
| +| `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` _[FlavorFilter](#flavorfilter)_ | 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: \{\}
| #### FlavorResourceSpec @@ -816,14 +816,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description contains a free form description of the flavor. | | MaxLength: 65535
MinLength: 1
| -| `ram` _integer_ | ram is the memory of the flavor, measured in MB. | | Minimum: 1
| -| `vcpus` _integer_ | vcpus is the number of vcpus for the flavor. | | Minimum: 1
| -| `disk` _integer_ | disk is the size of the root disk that will be created in GiB. If 0
the root disk will be set to exactly the size of the image used to
deploy the instance. However, in this case the scheduler cannot
select the compute host based on the virtual image size. Therefore,
0 should only be used for volume booted instances or for testing
purposes. Volume-backed instances can be enforced for flavors with
zero root disk via the
os_compute_api:servers:create:zero_disk_flavor policy rule. | | Minimum: 0
| -| `swap` _integer_ | swap is the size of a dedicated swap disk that will be allocated, in
MiB. If 0 (the default), no dedicated swap disk will be created. | | Minimum: 0
| -| `isPublic` _boolean_ | isPublic flags a flavor as being available to all projects or not. | | | -| `ephemeral` _integer_ | ephemeral is the size of the ephemeral disk that will be created, in GiB.
Ephemeral disks may be written over on server state changes. So should only
be used as a scratch space for applications that are aware of its
limitations. Defaults to 0. | | Minimum: 0
| +| `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 contains a free form description of the flavor. | | MaxLength: 65535
MinLength: 1
Optional: \{\}
| +| `ram` _integer_ | ram is the memory of the flavor, measured in MB. | | Minimum: 1
Required: \{\}
| +| `vcpus` _integer_ | vcpus is the number of vcpus for the flavor. | | Minimum: 1
Required: \{\}
| +| `disk` _integer_ | disk is the size of the root disk that will be created in GiB. If 0
the root disk will be set to exactly the size of the image used to
deploy the instance. However, in this case the scheduler cannot
select the compute host based on the virtual image size. Therefore,
0 should only be used for volume booted instances or for testing
purposes. Volume-backed instances can be enforced for flavors with
zero root disk via the
os_compute_api:servers:create:zero_disk_flavor policy rule. | | Minimum: 0
Required: \{\}
| +| `swap` _integer_ | swap is the size of a dedicated swap disk that will be allocated, in
MiB. If 0 (the default), no dedicated swap disk will be created. | | Minimum: 0
Optional: \{\}
| +| `isPublic` _boolean_ | isPublic flags a flavor as being available to all projects or not. | | Optional: \{\}
| +| `ephemeral` _integer_ | ephemeral is the size of the ephemeral disk that will be created, in GiB.
Ephemeral disks may be written over on server state changes. So should only
be used as a scratch space for applications that are aware of its
limitations. Defaults to 0. | | Minimum: 0
Optional: \{\}
| #### FlavorResourceStatus @@ -839,14 +839,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the flavor. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 65535
| -| `ram` _integer_ | ram is the memory of the flavor, measured in MB. | | | -| `vcpus` _integer_ | vcpus is the number of vcpus for the flavor. | | | -| `disk` _integer_ | disk is the size of the root disk that will be created in GiB. | | | -| `swap` _integer_ | swap is the size of a dedicated swap disk that will be allocated, in
MiB. | | | -| `isPublic` _boolean_ | isPublic flags a flavor as being available to all projects or not. | | | -| `ephemeral` _integer_ | ephemeral is the size of the ephemeral disk, in GiB. | | | +| `name` _string_ | name is a Human-readable name for the flavor. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 65535
Optional: \{\}
| +| `ram` _integer_ | ram is the memory of the flavor, measured in MB. | | Optional: \{\}
| +| `vcpus` _integer_ | vcpus is the number of vcpus for the flavor. | | Optional: \{\}
| +| `disk` _integer_ | disk is the size of the root disk that will be created in GiB. | | Optional: \{\}
| +| `swap` _integer_ | swap is the size of a dedicated swap disk that will be allocated, in
MiB. | | Optional: \{\}
| +| `isPublic` _boolean_ | isPublic flags a flavor as being available to all projects or not. | | Optional: \{\}
| +| `ephemeral` _integer_ | ephemeral is the size of the ephemeral disk, in GiB. | | Optional: \{\}
| #### FlavorSpec @@ -862,11 +862,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[FlavorImport](#flavorimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[FlavorResourceSpec](#flavorresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[FlavorImport](#flavorimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[FlavorResourceSpec](#flavorresourcespec)_ | 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: \{\}
| #### FlavorStatus @@ -882,9 +882,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[FlavorResourceStatus](#flavorresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[FlavorResourceStatus](#flavorresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### FloatingIP @@ -901,9 +901,9 @@ FloatingIP is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `FloatingIP` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[FloatingIPSpec](#floatingipspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[FloatingIPStatus](#floatingipstatus)_ | status defines the observed state of the resource. | | | +| `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` _[FloatingIPSpec](#floatingipspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[FloatingIPStatus](#floatingipstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### FloatingIPFilter @@ -920,16 +920,16 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `floatingIP` _[IPvAny](#ipvany)_ | floatingIP is the floatingip address. | | MaxLength: 45
MinLength: 1
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `floatingNetworkRef` _[KubernetesNameRef](#kubernetesnameref)_ | floatingNetworkRef is a reference to the ORC Network which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `status` _string_ | status is the status of the floatingip. | | MaxLength: 1024
| -| `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
| -| `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
| -| `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
| -| `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
| +| `floatingIP` _[IPvAny](#ipvany)_ | floatingIP is the floatingip address. | | MaxLength: 45
MinLength: 1
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `floatingNetworkRef` _[KubernetesNameRef](#kubernetesnameref)_ | floatingNetworkRef is a reference to the ORC Network which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `status` _string_ | status is the status of the floatingip. | | MaxLength: 1024
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: \{\}
| #### FloatingIPImport @@ -948,8 +948,8 @@ _Appears in:_ | 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
| -| `filter` _[FloatingIPFilter](#floatingipfilter)_ | 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
| +| `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` _[FloatingIPFilter](#floatingipfilter)_ | 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: \{\}
| #### FloatingIPResourceSpec @@ -965,14 +965,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the floatingip. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `floatingNetworkRef` _[KubernetesNameRef](#kubernetesnameref)_ | floatingNetworkRef references the network to which the floatingip is associated. | | MaxLength: 253
MinLength: 1
| -| `floatingSubnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | floatingSubnetRef references the subnet to which the floatingip is associated. | | MaxLength: 253
MinLength: 1
| -| `floatingIP` _[IPvAny](#ipvany)_ | floatingIP is the IP that will be assigned to the floatingip. If not set, it will
be assigned automatically. | | MaxLength: 45
MinLength: 1
| -| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `fixedIP` _[IPvAny](#ipvany)_ | fixedIP is the IP address of the port to which the floatingip is associated. | | MaxLength: 45
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| +| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the floatingip. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `floatingNetworkRef` _[KubernetesNameRef](#kubernetesnameref)_ | floatingNetworkRef references the network to which the floatingip is associated. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `floatingSubnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | floatingSubnetRef references the subnet to which the floatingip is associated. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `floatingIP` _[IPvAny](#ipvany)_ | floatingIP is the IP that will be assigned to the floatingip. If not set, it will
be assigned automatically. | | MaxLength: 45
MinLength: 1
Optional: \{\}
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `fixedIP` _[IPvAny](#ipvany)_ | fixedIP is the IP address of the port to which the floatingip is associated. | | MaxLength: 45
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### FloatingIPResourceStatus @@ -988,19 +988,19 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `floatingNetworkID` _string_ | floatingNetworkID is the ID of the network to which the floatingip is associated. | | MaxLength: 1024
| -| `floatingIP` _string_ | floatingIP is the IP address of the floatingip. | | MaxLength: 1024
| -| `portID` _string_ | portID is the ID of the port to which the floatingip is associated. | | MaxLength: 1024
| -| `fixedIP` _string_ | fixedIP is the IP address of the port to which the floatingip is associated. | | MaxLength: 1024
| -| `tenantID` _string_ | tenantID is the project owner of the resource. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
| -| `status` _string_ | status indicates the current status of the resource. | | MaxLength: 1024
| -| `routerID` _string_ | routerID is the ID of the router to which the floatingip is associated. | | MaxLength: 1024
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `floatingNetworkID` _string_ | floatingNetworkID is the ID of the network to which the floatingip is associated. | | MaxLength: 1024
Optional: \{\}
| +| `floatingIP` _string_ | floatingIP is the IP address of the floatingip. | | MaxLength: 1024
Optional: \{\}
| +| `portID` _string_ | portID is the ID of the port to which the floatingip is associated. | | MaxLength: 1024
Optional: \{\}
| +| `fixedIP` _string_ | fixedIP is the IP address of the port to which the floatingip is associated. | | MaxLength: 1024
Optional: \{\}
| +| `tenantID` _string_ | tenantID is the project owner of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status indicates the current status of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `routerID` _string_ | routerID is the ID of the router to which the floatingip is associated. | | MaxLength: 1024
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: \{\}
| #### FloatingIPSpec @@ -1016,11 +1016,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[FloatingIPImport](#floatingipimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[FloatingIPResourceSpec](#floatingipresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[FloatingIPImport](#floatingipimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[FloatingIPResourceSpec](#floatingipresourcespec)_ | 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: \{\}
| #### FloatingIPStatus @@ -1036,9 +1036,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[FloatingIPResourceStatus](#floatingipresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[FloatingIPResourceStatus](#floatingipresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Group @@ -1055,9 +1055,9 @@ Group is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Group` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[GroupSpec](#groupspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[GroupStatus](#groupstatus)_ | status defines the observed state of the resource. | | | +| `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` _[GroupSpec](#groupspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[GroupStatus](#groupstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### GroupFilter @@ -1074,8 +1074,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### GroupImport @@ -1094,8 +1094,8 @@ _Appears in:_ | 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
| -| `filter` _[GroupFilter](#groupfilter)_ | 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
| +| `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` _[GroupFilter](#groupfilter)_ | 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: \{\}
| #### GroupResourceSpec @@ -1111,9 +1111,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### GroupResourceStatus @@ -1129,9 +1129,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
| +| `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: \{\}
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| #### GroupSpec @@ -1147,11 +1147,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[GroupImport](#groupimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[GroupResourceSpec](#groupresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[GroupImport](#groupimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[GroupResourceSpec](#groupresourcespec)_ | 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: \{\}
| #### GroupStatus @@ -1167,9 +1167,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### HostID @@ -1188,8 +1188,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `id` _string_ | id is the literal host ID string to use for binding:host_id.
This is mutually exclusive with serverRef. | | MaxLength: 36
| -| `serverRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverRef is a reference to an ORC Server resource from which to
retrieve the hostID for port binding. The hostID will be read from
the Server's status.resource.hostID field.
This is mutually exclusive with id. | | MaxLength: 253
MinLength: 1
| +| `id` _string_ | id is the literal host ID string to use for binding:host_id.
This is mutually exclusive with serverRef. | | MaxLength: 36
Optional: \{\}
| +| `serverRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverRef is a reference to an ORC Server resource from which to
retrieve the hostID for port binding. The hostID will be read from
the Server's status.resource.hostID field.
This is mutually exclusive with id. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### HostRoute @@ -1205,8 +1205,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `destination` _[CIDR](#cidr)_ | destination for the additional route. | | Format: cidr
MaxLength: 49
MinLength: 1
| -| `nextHop` _[IPvAny](#ipvany)_ | nextHop for the additional route. | | MaxLength: 45
MinLength: 1
| +| `destination` _[CIDR](#cidr)_ | destination for the additional route. | | Format: cidr
MaxLength: 49
MinLength: 1
Required: \{\}
| +| `nextHop` _[IPvAny](#ipvany)_ | nextHop for the additional route. | | MaxLength: 45
MinLength: 1
Required: \{\}
| #### HostRouteStatus @@ -1222,8 +1222,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `destination` _string_ | destination for the additional route. | | MaxLength: 1024
| -| `nextHop` _string_ | nextHop for the additional route. | | MaxLength: 1024
| +| `destination` _string_ | destination for the additional route. | | MaxLength: 1024
Optional: \{\}
| +| `nextHop` _string_ | nextHop for the additional route. | | MaxLength: 1024
Optional: \{\}
| #### IPVersion @@ -1272,8 +1272,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `addressMode` _[IPv6AddressMode](#ipv6addressmode)_ | addressMode specifies mechanisms for assigning IPv6 IP addresses. | | Enum: [slaac dhcpv6-stateful dhcpv6-stateless]
| -| `raMode` _[IPv6RAMode](#ipv6ramode)_ | raMode specifies the IPv6 router advertisement mode. It specifies whether
the networking service should transmit ICMPv6 packets. | | Enum: [slaac dhcpv6-stateful dhcpv6-stateless]
| +| `addressMode` _[IPv6AddressMode](#ipv6addressmode)_ | addressMode specifies mechanisms for assigning IPv6 IP addresses. | | Enum: [slaac dhcpv6-stateful dhcpv6-stateless]
Optional: \{\}
| +| `raMode` _[IPv6RAMode](#ipv6ramode)_ | raMode specifies the IPv6 router advertisement mode. It specifies whether
the networking service should transmit ICMPv6 packets. | | Enum: [slaac dhcpv6-stateful dhcpv6-stateless]
Optional: \{\}
| #### IPv6RAMode @@ -1327,9 +1327,9 @@ Image is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Image` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[ImageSpec](#imagespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[ImageStatus](#imagestatus)_ | status defines the observed state of the resource. | | | +| `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` _[ImageSpec](#imagespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ImageStatus](#imagestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### ImageCompression @@ -1388,9 +1388,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `containerFormat` _[ImageContainerFormat](#imagecontainerformat)_ | containerFormat is the format of the image container.
qcow2 and raw images do not usually have a container. This is specified as "bare", which is also the default.
Permitted values are ami, ari, aki, bare, compressed, ovf, ova, and docker. | bare | Enum: [ami ari aki bare ovf ova docker compressed]
| -| `diskFormat` _[ImageDiskFormat](#imagediskformat)_ | diskFormat is the format of the disk image.
Normal values are "qcow2", or "raw". Glance may be configured to support others. | | Enum: [ami ari aki vhd vhdx vmdk raw qcow2 vdi ploop iso]
| -| `download` _[ImageContentSourceDownload](#imagecontentsourcedownload)_ | download describes how to obtain image data by downloading it from a URL.
Must be set when creating a managed image. | | | +| `containerFormat` _[ImageContainerFormat](#imagecontainerformat)_ | containerFormat is the format of the image container.
qcow2 and raw images do not usually have a container. This is specified as "bare", which is also the default.
Permitted values are ami, ari, aki, bare, compressed, ovf, ova, and docker. | bare | Enum: [ami ari aki bare ovf ova docker compressed]
Optional: \{\}
| +| `diskFormat` _[ImageDiskFormat](#imagediskformat)_ | diskFormat is the format of the disk image.
Normal values are "qcow2", or "raw". Glance may be configured to support others. | | Enum: [ami ari aki vhd vhdx vmdk raw qcow2 vdi ploop iso]
Required: \{\}
| +| `download` _[ImageContentSourceDownload](#imagecontentsourcedownload)_ | download describes how to obtain image data by downloading it from a URL.
Must be set when creating a managed image. | | Required: \{\}
| #### ImageContentSourceDownload @@ -1406,9 +1406,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `url` _string_ | url containing image data | | Format: uri
MaxLength: 2048
| -| `decompress` _[ImageCompression](#imagecompression)_ | decompress specifies that the source data must be decompressed with the
given compression algorithm before being stored. Specifying Decompress
will disable the use of Glance's web-download, as web-download cannot
currently deterministically decompress downloaded content. | | Enum: [xz gz bz2]
| -| `hash` _[ImageHash](#imagehash)_ | hash is a hash which will be used to verify downloaded data, i.e.
before any decompression. If not specified, no hash verification will be
performed. Specifying a Hash will disable the use of Glance's
web-download, as web-download cannot currently deterministically verify
the hash of downloaded content. | | | +| `url` _string_ | url containing image data | | Format: uri
MaxLength: 2048
Required: \{\}
| +| `decompress` _[ImageCompression](#imagecompression)_ | decompress specifies that the source data must be decompressed with the
given compression algorithm before being stored. Specifying Decompress
will disable the use of Glance's web-download, as web-download cannot
currently deterministically decompress downloaded content. | | Enum: [xz gz bz2]
Optional: \{\}
| +| `hash` _[ImageHash](#imagehash)_ | hash is a hash which will be used to verify downloaded data, i.e.
before any decompression. If not specified, no hash verification will be
performed. Specifying a Hash will disable the use of Glance's
web-download, as web-download cannot currently deterministically verify
the hash of downloaded content. | | Optional: \{\}
| #### ImageDiskFormat @@ -1452,9 +1452,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name specifies the name of a Glance image | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `visibility` _[ImageVisibility](#imagevisibility)_ | visibility specifies the visibility of a Glance image. | | Enum: [public private shared community]
| -| `tags` _[ImageTag](#imagetag) array_ | tags is the list of tags on the resource. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| +| `name` _[OpenStackName](#openstackname)_ | name specifies the name of a Glance image | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `visibility` _[ImageVisibility](#imagevisibility)_ | visibility specifies the visibility of a Glance image. | | Enum: [public private shared community]
Optional: \{\}
| +| `tags` _[ImageTag](#imagetag) array_ | tags is the list of tags on the resource. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| #### ImageHWBus @@ -1487,8 +1487,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `algorithm` _[ImageHashAlgorithm](#imagehashalgorithm)_ | algorithm is the hash algorithm used to generate value. | | Enum: [md5 sha1 sha256 sha512]
| -| `value` _string_ | value is the hash of the image data using Algorithm. It must be hex encoded using lowercase letters. | | MaxLength: 1024
MinLength: 1
Pattern: `^[0-9a-f]+$`
| +| `algorithm` _[ImageHashAlgorithm](#imagehashalgorithm)_ | algorithm is the hash algorithm used to generate value. | | Enum: [md5 sha1 sha256 sha512]
Required: \{\}
| +| `value` _string_ | value is the hash of the image data using Algorithm. It must be hex encoded using lowercase letters. | | MaxLength: 1024
MinLength: 1
Pattern: `^[0-9a-f]+$`
Required: \{\}
| #### ImageHashAlgorithm @@ -1527,8 +1527,8 @@ _Appears in:_ | 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
| -| `filter` _[ImageFilter](#imagefilter)_ | 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
| +| `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` _[ImageFilter](#imagefilter)_ | 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: \{\}
| #### ImageProperties @@ -1544,12 +1544,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `architecture` _string_ | architecture is the CPU architecture that must be supported by the hypervisor. | | Enum: [aarch64 alpha armv7l cris i686 ia64 lm32 m68k microblaze microblazeel mips mipsel mips64 mips64el openrisc parisc parisc64 ppc ppc64 ppcemb s390 s390x sh4 sh4eb sparc sparc64 unicore32 x86_64 xtensa xtensaeb]
| -| `hypervisorType` _string_ | hypervisorType is the hypervisor type | | Enum: [hyperv ironic lxc qemu uml vmware xen]
| -| `minDiskGB` _integer_ | minDiskGB is the minimum amount of disk space in GB that is required to boot the image | | Minimum: 1
| -| `minMemoryMB` _integer_ | minMemoryMB is the minimum amount of RAM in MB that is required to boot the image. | | Minimum: 1
| -| `hardware` _[ImagePropertiesHardware](#imagepropertieshardware)_ | hardware is a set of properties which control the virtual hardware
created by Nova. | | | -| `operatingSystem` _[ImagePropertiesOperatingSystem](#imagepropertiesoperatingsystem)_ | operatingSystem is a set of properties that specify and influence the behavior
of the operating system within the virtual machine. | | | +| `architecture` _string_ | architecture is the CPU architecture that must be supported by the hypervisor. | | Enum: [aarch64 alpha armv7l cris i686 ia64 lm32 m68k microblaze microblazeel mips mipsel mips64 mips64el openrisc parisc parisc64 ppc ppc64 ppcemb s390 s390x sh4 sh4eb sparc sparc64 unicore32 x86_64 xtensa xtensaeb]
Optional: \{\}
| +| `hypervisorType` _string_ | hypervisorType is the hypervisor type | | Enum: [hyperv ironic lxc qemu uml vmware xen]
Optional: \{\}
| +| `minDiskGB` _integer_ | minDiskGB is the minimum amount of disk space in GB that is required to boot the image | | Minimum: 1
Optional: \{\}
| +| `minMemoryMB` _integer_ | minMemoryMB is the minimum amount of RAM in MB that is required to boot the image. | | Minimum: 1
Optional: \{\}
| +| `hardware` _[ImagePropertiesHardware](#imagepropertieshardware)_ | hardware is a set of properties which control the virtual hardware
created by Nova. | | Optional: \{\}
| +| `operatingSystem` _[ImagePropertiesOperatingSystem](#imagepropertiesoperatingsystem)_ | operatingSystem is a set of properties that specify and influence the behavior
of the operating system within the virtual machine. | | Optional: \{\}
| #### ImagePropertiesHardware @@ -1565,17 +1565,17 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `cpuSockets` _integer_ | cpuSockets is the preferred number of sockets to expose to the guest | | Minimum: 1
| -| `cpuCores` _integer_ | cpuCores is the preferred number of cores to expose to the guest | | Minimum: 1
| -| `cpuThreads` _integer_ | cpuThreads is the preferred number of threads to expose to the guest | | Minimum: 1
| -| `cpuPolicy` _string_ | cpuPolicy is used to pin the virtual CPUs (vCPUs) of instances to the
host's physical CPU cores (pCPUs). Host aggregates should be used to
separate these pinned instances from unpinned instances as the latter
will not respect the resourcing requirements of the former.
Permitted values are shared (the default), and dedicated.
shared: The guest vCPUs will be allowed to freely float across host
pCPUs, albeit potentially constrained by NUMA policy.
dedicated: The guest vCPUs will be strictly pinned to a set of host
pCPUs. In the absence of an explicit vCPU topology request, the
drivers typically expose all vCPUs as sockets with one core and one
thread. When strict CPU pinning is in effect the guest CPU topology
will be setup to match the topology of the CPUs to which it is
pinned. This option implies an overcommit ratio of 1.0. For example,
if a two vCPU guest is pinned to a single host core with two threads,
then the guest will get a topology of one socket, one core, two
threads. | | Enum: [shared dedicated]
| -| `cpuThreadPolicy` _string_ | cpuThreadPolicy further refines a CPUPolicy of 'dedicated' by stating
how hardware CPU threads in a simultaneous multithreading-based (SMT)
architecture be used. SMT-based architectures include Intel
processors with Hyper-Threading technology. In these architectures,
processor cores share a number of components with one or more other
cores. Cores in such architectures are commonly referred to as
hardware threads, while the cores that a given core share components
with are known as thread siblings.
Permitted values are prefer (the default), isolate, and require.
prefer: The host may or may not have an SMT architecture. Where an
SMT architecture is present, thread siblings are preferred.
isolate: The host must not have an SMT architecture or must emulate a
non-SMT architecture. If the host does not have an SMT architecture,
each vCPU is placed on a different core as expected. If the host does
have an SMT architecture - that is, one or more cores have thread
siblings - then each vCPU is placed on a different physical core. No
vCPUs from other guests are placed on the same core. All but one
thread sibling on each utilized core is therefore guaranteed to be
unusable.
require: The host must have an SMT architecture. Each vCPU is
allocated on thread siblings. If the host does not have an SMT
architecture, then it is not used. If the host has an SMT
architecture, but not enough cores with free thread siblings are
available, then scheduling fails. | | Enum: [prefer isolate require]
| -| `cdromBus` _[ImageHWBus](#imagehwbus)_ | cdromBus specifies the type of disk controller to attach CD-ROM devices to. | | Enum: [scsi virtio uml xen ide usb lxc]
| -| `diskBus` _[ImageHWBus](#imagehwbus)_ | diskBus specifies the type of disk controller to attach disk devices to. | | Enum: [scsi virtio uml xen ide usb lxc]
| -| `scsiModel` _string_ | scsiModel enables the use of VirtIO SCSI (virtio-scsi) to provide
block device access for compute instances; by default, instances use
VirtIO Block (virtio-blk). VirtIO SCSI is a para-virtualized SCSI
controller device that provides improved scalability and performance,
and supports advanced SCSI hardware.
The only permitted value is virtio-scsi. | | Enum: [virtio-scsi]
| -| `vifModel` _string_ | vifModel specifies the model of virtual network interface device to use.
Permitted values are e1000, e1000e, ne2k_pci, pcnet, rtl8139, virtio,
and vmxnet3. | | Enum: [e1000 e1000e ne2k_pci pcnet rtl8139 virtio vmxnet3]
| -| `rngModel` _string_ | rngModel adds a random-number generator device to the image’s instances.
This image property by itself does not guarantee that a hardware RNG will be used;
it expresses a preference that may or may not be satisfied depending upon Nova configuration. | | MaxLength: 255
| -| `qemuGuestAgent` _boolean_ | qemuGuestAgent enables QEMU guest agent. | | | +| `cpuSockets` _integer_ | cpuSockets is the preferred number of sockets to expose to the guest | | Minimum: 1
Optional: \{\}
| +| `cpuCores` _integer_ | cpuCores is the preferred number of cores to expose to the guest | | Minimum: 1
Optional: \{\}
| +| `cpuThreads` _integer_ | cpuThreads is the preferred number of threads to expose to the guest | | Minimum: 1
Optional: \{\}
| +| `cpuPolicy` _string_ | cpuPolicy is used to pin the virtual CPUs (vCPUs) of instances to the
host's physical CPU cores (pCPUs). Host aggregates should be used to
separate these pinned instances from unpinned instances as the latter
will not respect the resourcing requirements of the former.
Permitted values are shared (the default), and dedicated.
shared: The guest vCPUs will be allowed to freely float across host
pCPUs, albeit potentially constrained by NUMA policy.
dedicated: The guest vCPUs will be strictly pinned to a set of host
pCPUs. In the absence of an explicit vCPU topology request, the
drivers typically expose all vCPUs as sockets with one core and one
thread. When strict CPU pinning is in effect the guest CPU topology
will be setup to match the topology of the CPUs to which it is
pinned. This option implies an overcommit ratio of 1.0. For example,
if a two vCPU guest is pinned to a single host core with two threads,
then the guest will get a topology of one socket, one core, two
threads. | | Enum: [shared dedicated]
Optional: \{\}
| +| `cpuThreadPolicy` _string_ | cpuThreadPolicy further refines a CPUPolicy of 'dedicated' by stating
how hardware CPU threads in a simultaneous multithreading-based (SMT)
architecture be used. SMT-based architectures include Intel
processors with Hyper-Threading technology. In these architectures,
processor cores share a number of components with one or more other
cores. Cores in such architectures are commonly referred to as
hardware threads, while the cores that a given core share components
with are known as thread siblings.
Permitted values are prefer (the default), isolate, and require.
prefer: The host may or may not have an SMT architecture. Where an
SMT architecture is present, thread siblings are preferred.
isolate: The host must not have an SMT architecture or must emulate a
non-SMT architecture. If the host does not have an SMT architecture,
each vCPU is placed on a different core as expected. If the host does
have an SMT architecture - that is, one or more cores have thread
siblings - then each vCPU is placed on a different physical core. No
vCPUs from other guests are placed on the same core. All but one
thread sibling on each utilized core is therefore guaranteed to be
unusable.
require: The host must have an SMT architecture. Each vCPU is
allocated on thread siblings. If the host does not have an SMT
architecture, then it is not used. If the host has an SMT
architecture, but not enough cores with free thread siblings are
available, then scheduling fails. | | Enum: [prefer isolate require]
Optional: \{\}
| +| `cdromBus` _[ImageHWBus](#imagehwbus)_ | cdromBus specifies the type of disk controller to attach CD-ROM devices to. | | Enum: [scsi virtio uml xen ide usb lxc]
Optional: \{\}
| +| `diskBus` _[ImageHWBus](#imagehwbus)_ | diskBus specifies the type of disk controller to attach disk devices to. | | Enum: [scsi virtio uml xen ide usb lxc]
Optional: \{\}
| +| `scsiModel` _string_ | scsiModel enables the use of VirtIO SCSI (virtio-scsi) to provide
block device access for compute instances; by default, instances use
VirtIO Block (virtio-blk). VirtIO SCSI is a para-virtualized SCSI
controller device that provides improved scalability and performance,
and supports advanced SCSI hardware.
The only permitted value is virtio-scsi. | | Enum: [virtio-scsi]
Optional: \{\}
| +| `vifModel` _string_ | vifModel specifies the model of virtual network interface device to use.
Permitted values are e1000, e1000e, ne2k_pci, pcnet, rtl8139, virtio,
and vmxnet3. | | Enum: [e1000 e1000e ne2k_pci pcnet rtl8139 virtio vmxnet3]
Optional: \{\}
| +| `rngModel` _string_ | rngModel adds a random-number generator device to the image’s instances.
This image property by itself does not guarantee that a hardware RNG will be used;
it expresses a preference that may or may not be satisfied depending upon Nova configuration. | | MaxLength: 255
Optional: \{\}
| +| `qemuGuestAgent` _boolean_ | qemuGuestAgent enables QEMU guest agent. | | Optional: \{\}
| #### ImagePropertiesOperatingSystem @@ -1591,8 +1591,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `distro` _string_ | distro is the common name of the operating system distribution in lowercase. | | Enum: [arch centos debian fedora freebsd gentoo mandrake mandriva mes msdos netbsd netware openbsd opensolaris opensuse rocky rhel sled ubuntu windows]
| -| `version` _string_ | version is the operating system version as specified by the distributor. | | MaxLength: 255
| +| `distro` _string_ | distro is the common name of the operating system distribution in lowercase. | | Enum: [arch centos debian fedora freebsd gentoo mandrake mandriva mes msdos netbsd netware openbsd opensolaris opensuse rocky rhel sled ubuntu windows]
Optional: \{\}
| +| `version` _string_ | version is the operating system version as specified by the distributor. | | MaxLength: 255
Optional: \{\}
| #### ImageResourceSpec @@ -1608,12 +1608,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created Glance image. If not specified, the
name of the Image object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `protected` _boolean_ | protected specifies that the image is protected from deletion.
If not specified, the default is false. | | | -| `tags` _[ImageTag](#imagetag) array_ | tags is a list of tags which will be applied to the image. A tag has a maximum length of 255 characters. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `visibility` _[ImageVisibility](#imagevisibility)_ | visibility of the image | | Enum: [public private shared community]
| -| `properties` _[ImageProperties](#imageproperties)_ | properties is metadata available to consumers of the image | | | -| `content` _[ImageContent](#imagecontent)_ | content specifies how to obtain the image content. | | | +| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created Glance image. If not specified, the
name of the Image object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `protected` _boolean_ | protected specifies that the image is protected from deletion.
If not specified, the default is false. | | Optional: \{\}
| +| `tags` _[ImageTag](#imagetag) array_ | tags is a list of tags which will be applied to the image. A tag has a maximum length of 255 characters. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `visibility` _[ImageVisibility](#imagevisibility)_ | visibility of the image | | Enum: [public private shared community]
Optional: \{\}
| +| `properties` _[ImageProperties](#imageproperties)_ | properties is metadata available to consumers of the image | | Optional: \{\}
| +| `content` _[ImageContent](#imagecontent)_ | content specifies how to obtain the image content. | | Optional: \{\}
| #### ImageResourceStatus @@ -1629,14 +1629,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the image. Might not be unique. | | MaxLength: 1024
| -| `status` _string_ | status is the image status as reported by Glance | | MaxLength: 1024
| -| `protected` _boolean_ | protected specifies that the image is protected from deletion. | | | -| `visibility` _string_ | visibility of the image | | MaxLength: 1024
| -| `hash` _[ImageHash](#imagehash)_ | hash is the hash of the image data published by Glance. Note that this is
a hash of the data stored internally by Glance, which will have been
decompressed and potentially format converted depending on server-side
configuration which is not visible to clients. It is expected that this
hash will usually differ from the download hash. | | | -| `sizeB` _integer_ | sizeB is the size of the image data, in bytes | | | -| `virtualSizeB` _integer_ | virtualSizeB is the size of the disk the image data represents, in bytes | | | -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| +| `name` _string_ | name is a Human-readable name for the image. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status is the image status as reported by Glance | | MaxLength: 1024
Optional: \{\}
| +| `protected` _boolean_ | protected specifies that the image is protected from deletion. | | Optional: \{\}
| +| `visibility` _string_ | visibility of the image | | MaxLength: 1024
Optional: \{\}
| +| `hash` _[ImageHash](#imagehash)_ | hash is the hash of the image data published by Glance. Note that this is
a hash of the data stored internally by Glance, which will have been
decompressed and potentially format converted depending on server-side
configuration which is not visible to clients. It is expected that this
hash will usually differ from the download hash. | | Optional: \{\}
| +| `sizeB` _integer_ | sizeB is the size of the image data, in bytes | | Optional: \{\}
| +| `virtualSizeB` _integer_ | virtualSizeB is the size of the disk the image data represents, in bytes | | Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| #### ImageSpec @@ -1652,11 +1652,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[ImageImport](#imageimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[ImageResourceSpec](#imageresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[ImageImport](#imageimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ImageResourceSpec](#imageresourcespec)_ | 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: \{\}
| #### ImageStatus @@ -1672,10 +1672,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[ImageResourceStatus](#imageresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | -| `downloadAttempts` _integer_ | downloadAttempts is the number of times the controller has attempted to download the image contents | | | +| `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` _[ImageResourceStatus](#imageresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `downloadAttempts` _integer_ | downloadAttempts is the number of times the controller has attempted to download the image contents | | Optional: \{\}
| #### ImageStatusExtra @@ -1691,7 +1691,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `downloadAttempts` _integer_ | downloadAttempts is the number of times the controller has attempted to download the image contents | | | +| `downloadAttempts` _integer_ | downloadAttempts is the number of times the controller has attempted to download the image contents | | Optional: \{\}
| #### ImageTag @@ -1745,9 +1745,9 @@ KeyPair is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `KeyPair` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[KeyPairSpec](#keypairspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[KeyPairStatus](#keypairstatus)_ | status defines the observed state of the resource. | | | +| `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` _[KeyPairSpec](#keypairspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[KeyPairStatus](#keypairstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### KeyPairFilter @@ -1764,7 +1764,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing Keypair | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing Keypair | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| #### KeyPairImport @@ -1783,8 +1783,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `id` _string_ | id contains the name of an existing resource. Note: This resource uses
the resource name as the unique identifier, not a UUID.
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. | | MaxLength: 1024
| -| `filter` _[KeyPairFilter](#keypairfilter)_ | 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
| +| `id` _string_ | id contains the name of an existing resource. Note: This resource uses
the resource name as the unique identifier, not a UUID.
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. | | MaxLength: 1024
Optional: \{\}
| +| `filter` _[KeyPairFilter](#keypairfilter)_ | 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: \{\}
| #### KeyPairResourceSpec @@ -1800,9 +1800,9 @@ _Appears in:_ | 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: `^[^,]+$`
| -| `type` _string_ | type specifies the type of the Keypair. Allowed values are ssh or x509.
If not specified, defaults to ssh. | | Enum: [ssh x509]
| -| `publicKey` _string_ | publicKey is the public key to import. | | MaxLength: 16384
MinLength: 1
| +| `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: \{\}
| +| `type` _string_ | type specifies the type of the Keypair. Allowed values are ssh or x509.
If not specified, defaults to ssh. | | Enum: [ssh x509]
Optional: \{\}
| +| `publicKey` _string_ | publicKey is the public key to import. | | MaxLength: 16384
MinLength: 1
Required: \{\}
| #### KeyPairResourceStatus @@ -1818,10 +1818,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `fingerprint` _string_ | fingerprint is the fingerprint of the public key | | MaxLength: 1024
| -| `publicKey` _string_ | publicKey is the public key of the Keypair | | MaxLength: 16384
| -| `type` _string_ | type is the type of the Keypair (ssh or x509) | | MaxLength: 64
| +| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `fingerprint` _string_ | fingerprint is the fingerprint of the public key | | MaxLength: 1024
Optional: \{\}
| +| `publicKey` _string_ | publicKey is the public key of the Keypair | | MaxLength: 16384
Optional: \{\}
| +| `type` _string_ | type is the type of the Keypair (ssh or x509) | | MaxLength: 64
Optional: \{\}
| #### KeyPairSpec @@ -1837,11 +1837,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[KeyPairImport](#keypairimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[KeyPairResourceSpec](#keypairresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[KeyPairImport](#keypairimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[KeyPairResourceSpec](#keypairresourcespec)_ | 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: \{\}
| #### KeyPairStatus @@ -1857,9 +1857,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[KeyPairResourceStatus](#keypairresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[KeyPairResourceStatus](#keypairresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### KeystoneName @@ -2013,7 +2013,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `onDelete` _[OnDelete](#ondelete)_ | 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`. | delete | Enum: [delete detach]
| +| `onDelete` _[OnDelete](#ondelete)_ | 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`. | delete | Enum: [delete detach]
Optional: \{\}
| #### ManagementPolicy @@ -2069,9 +2069,9 @@ Network is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Network` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[NetworkSpec](#networkspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[NetworkStatus](#networkstatus)_ | status defines the observed state of the resource. | | | +| `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` _[NetworkSpec](#networkspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[NetworkStatus](#networkstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### NetworkFilter @@ -2088,14 +2088,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `external` _boolean_ | external indicates whether the network has an external routing
facility that’s not managed by the networking service. | | | -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `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
| -| `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
| -| `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
| -| `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
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `external` _boolean_ | external indicates whether the network has an external routing
facility that’s not managed by the networking service. | | Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
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: \{\}
| #### NetworkImport @@ -2114,8 +2114,8 @@ _Appears in:_ | 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
| -| `filter` _[NetworkFilter](#networkfilter)_ | 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
| +| `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` _[NetworkFilter](#networkfilter)_ | 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: \{\}
| #### NetworkResourceSpec @@ -2131,17 +2131,17 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the network. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the network, which is up (true) or down (false) | | | -| `dnsDomain` _[DNSDomain](#dnsdomain)_ | dnsDomain is the DNS domain of the network | | MaxLength: 255
MinLength: 1
Pattern: `^[A-Za-z0-9]\{1,63\}(.[A-Za-z0-9-]\{1,63\})*(.[A-Za-z]\{2,63\})*.?$`
| -| `mtu` _[MTU](#mtu)_ | mtu is the the maximum transmission unit value to address
fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6.
Defaults to 1500. | | Maximum: 9216
Minimum: 68
| -| `portSecurityEnabled` _boolean_ | portSecurityEnabled is the port security status of the network.
Valid values are enabled (true) and disabled (false). This value is
used as the default value of port_security_enabled field of a newly
created port. | | | -| `external` _boolean_ | external indicates whether the network has an external routing
facility that’s not managed by the networking service. | | | -| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects. By default, only administrative users can change this
value. | | | -| `availabilityZoneHints` _[AvailabilityZoneHint](#availabilityzonehint) array_ | availabilityZoneHints is the availability zone candidate for the network. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| +| `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` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the network. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the network, which is up (true) or down (false) | | Optional: \{\}
| +| `dnsDomain` _[DNSDomain](#dnsdomain)_ | dnsDomain is the DNS domain of the network | | MaxLength: 255
MinLength: 1
Pattern: `^[A-Za-z0-9]\{1,63\}(.[A-Za-z0-9-]\{1,63\})*(.[A-Za-z]\{2,63\})*.?$`
Optional: \{\}
| +| `mtu` _[MTU](#mtu)_ | mtu is the the maximum transmission unit value to address
fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6.
Defaults to 1500. | | Maximum: 9216
Minimum: 68
Optional: \{\}
| +| `portSecurityEnabled` _boolean_ | portSecurityEnabled is the port security status of the network.
Valid values are enabled (true) and disabled (false). This value is
used as the default value of port_security_enabled field of a newly
created port. | | Optional: \{\}
| +| `external` _boolean_ | external indicates whether the network has an external routing
facility that’s not managed by the networking service. | | Optional: \{\}
| +| `shared` _boolean_ | shared indicates whether this resource is shared across all
projects. By default, only administrative users can change this
value. | | Optional: \{\}
| +| `availabilityZoneHints` _[AvailabilityZoneHint](#availabilityzonehint) array_ | availabilityZoneHints is the availability zone candidate for the network. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### NetworkResourceStatus @@ -2157,23 +2157,23 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the network. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the network. | | MaxLength: 1024
| -| `status` _string_ | status indicates whether network is currently operational. Possible values
include `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define
additional values. | | MaxLength: 1024
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the network,
which is up (true) or down (false). | | | -| `availabilityZoneHints` _string array_ | availabilityZoneHints is the availability zone candidate for the
network. | | MaxItems: 64
items:MaxLength: 1024
| -| `dnsDomain` _string_ | dnsDomain is the DNS domain of the network | | MaxLength: 1024
| -| `mtu` _integer_ | mtu is the the maximum transmission unit value to address
fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6. | | | -| `portSecurityEnabled` _boolean_ | portSecurityEnabled is the port security status of the network.
Valid values are enabled (true) and disabled (false). This value is
used as the default value of port_security_enabled field of a newly
created port. | | | -| `provider` _[ProviderPropertiesStatus](#providerpropertiesstatus)_ | provider contains provider-network properties. | | | -| `external` _boolean_ | external defines whether the network may be used for creation of
floating IPs. Only networks with this flag may be an external
gateway for routers. The network must have an external routing
facility that is not managed by the networking service. If the
network is updated from external to internal the unused floating IPs
of this network are automatically deleted when extension
floatingip-autodelete-internal is present. | | | -| `shared` _boolean_ | shared specifies whether the network resource can be accessed by any
tenant. | | | -| `subnets` _string array_ | subnets associated with this network. | | MaxItems: 256
items:MaxLength: 1024
| +| `name` _string_ | name is a Human-readable name for the network. 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 project owner of the network. | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status indicates whether network is currently operational. Possible values
include `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define
additional values. | | MaxLength: 1024
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: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the network,
which is up (true) or down (false). | | Optional: \{\}
| +| `availabilityZoneHints` _string array_ | availabilityZoneHints is the availability zone candidate for the
network. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `dnsDomain` _string_ | dnsDomain is the DNS domain of the network | | MaxLength: 1024
Optional: \{\}
| +| `mtu` _integer_ | mtu is the the maximum transmission unit value to address
fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6. | | Optional: \{\}
| +| `portSecurityEnabled` _boolean_ | portSecurityEnabled is the port security status of the network.
Valid values are enabled (true) and disabled (false). This value is
used as the default value of port_security_enabled field of a newly
created port. | | Optional: \{\}
| +| `provider` _[ProviderPropertiesStatus](#providerpropertiesstatus)_ | provider contains provider-network properties. | | Optional: \{\}
| +| `external` _boolean_ | external defines whether the network may be used for creation of
floating IPs. Only networks with this flag may be an external
gateway for routers. The network must have an external routing
facility that is not managed by the networking service. If the
network is updated from external to internal the unused floating IPs
of this network are automatically deleted when extension
floatingip-autodelete-internal is present. | | Optional: \{\}
| +| `shared` _boolean_ | shared specifies whether the network resource can be accessed by any
tenant. | | Optional: \{\}
| +| `subnets` _string array_ | subnets associated with this network. | | MaxItems: 256
items:MaxLength: 1024
Optional: \{\}
| #### NetworkSpec @@ -2189,11 +2189,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[NetworkImport](#networkimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[NetworkResourceSpec](#networkresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[NetworkImport](#networkimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[NetworkResourceSpec](#networkresourcespec)_ | 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: \{\}
| #### NetworkStatus @@ -2209,9 +2209,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[NetworkResourceStatus](#networkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[NetworkResourceStatus](#networkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### NeutronDescription @@ -2261,9 +2261,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | +| `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: \{\}
| #### NeutronTag @@ -2377,9 +2377,9 @@ Port is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Port` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[PortSpec](#portspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[PortStatus](#portstatus)_ | status defines the observed state of the resource. | | | +| `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` _[PortSpec](#portspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[PortStatus](#portstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### PortFilter @@ -2396,16 +2396,16 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this port is associated with. | | MaxLength: 253
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). | | | -| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| -| `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
| -| `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
| -| `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
| -| `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
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this port is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). | | Optional: \{\}
| +| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
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: \{\}
| #### PortImport @@ -2424,8 +2424,8 @@ _Appears in:_ | 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
| -| `filter` _[PortFilter](#portfilter)_ | 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
| +| `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` _[PortFilter](#portfilter)_ | 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: \{\}
| #### PortNumber @@ -2456,8 +2456,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `min` _[PortNumber](#portnumber)_ | min is the minimum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be less than or equal
to the port_range_max attribute value. If the protocol is ICMP, this value must be an ICMP type | | Maximum: 65535
Minimum: 0
| -| `max` _[PortNumber](#portnumber)_ | max is the maximum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be greater than or equal
to the port_range_min attribute value. If the protocol is ICMP, this value must be an ICMP code. | | Maximum: 65535
Minimum: 0
| +| `min` _[PortNumber](#portnumber)_ | min is the minimum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be less than or equal
to the port_range_max attribute value. If the protocol is ICMP, this value must be an ICMP type | | Maximum: 65535
Minimum: 0
Required: \{\}
| +| `max` _[PortNumber](#portnumber)_ | max is the maximum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be greater than or equal
to the port_range_min attribute value. If the protocol is ICMP, this value must be an ICMP code. | | Maximum: 65535
Minimum: 0
Required: \{\}
| #### PortRangeStatus @@ -2473,8 +2473,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `min` _integer_ | min is the minimum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be less than or equal
to the port_range_max attribute value. If the protocol is ICMP, this value must be an ICMP type | | | -| `max` _integer_ | max is the maximum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be greater than or equal
to the port_range_min attribute value. If the protocol is ICMP, this value must be an ICMP code. | | | +| `min` _integer_ | min is the minimum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be less than or equal
to the port_range_max attribute value. If the protocol is ICMP, this value must be an ICMP type | | Optional: \{\}
| +| `max` _integer_ | max is the maximum port number in the range that is matched by the security group rule.
If the protocol is TCP, UDP, DCCP, SCTP or UDP-Lite this value must be greater than or equal
to the port_range_min attribute value. If the protocol is ICMP, this value must be an ICMP code. | | Optional: \{\}
| #### PortResourceSpec @@ -2490,19 +2490,19 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name is a human-readable name of the port. If not set, the object's name will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this port is associated with. | | MaxLength: 253
MinLength: 1
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the port. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `allowedAddressPairs` _[AllowedAddressPair](#allowedaddresspair) array_ | allowedAddressPairs are allowed addresses associated with this port. | | MaxItems: 128
| -| `addresses` _[Address](#address) array_ | addresses are the IP addresses for the port. | | MaxItems: 128
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). The default value is true. | true | | -| `securityGroupRefs` _[OpenStackName](#openstackname) array_ | securityGroupRefs are the names of the security groups associated
with this port. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `vnicType` _string_ | vnicType specifies the type of vNIC which this port should be
attached to. This is used to determine which mechanism driver(s) to
be used to bind the port. The valid values are normal, macvtap,
direct, baremetal, direct-physical, virtio-forwarder, smart-nic and
remote-managed, although these values will not be validated in this
API to ensure compatibility with future neutron changes or custom
implementations. What type of vNIC is actually available depends on
deployments. If not specified, the Neutron default value is used. | | MaxLength: 64
| -| `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| -| `hostID` _[HostID](#hostid)_ | hostID specifies the host where the port will be bound.
Note that when the port is attached to a server, OpenStack may
rebind the port to the server's actual compute host, which may
differ from the specified hostID if no matching scheduler hint
is used. In this case the port's status will reflect the actual
binding host, not the value specified here. | | MaxProperties: 1
MinProperties: 1
| +| `name` _[OpenStackName](#openstackname)_ | name is a human-readable name of the port. If not set, the object's name will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this port is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the port. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `allowedAddressPairs` _[AllowedAddressPair](#allowedaddresspair) array_ | allowedAddressPairs are allowed addresses associated with this port. | | MaxItems: 128
Optional: \{\}
| +| `addresses` _[Address](#address) array_ | addresses are the IP addresses for the port. | | MaxItems: 128
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). The default value is true. | true | Optional: \{\}
| +| `securityGroupRefs` _[OpenStackName](#openstackname) array_ | securityGroupRefs are the names of the security groups associated
with this port. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `vnicType` _string_ | vnicType specifies the type of vNIC which this port should be
attached to. This is used to determine which mechanism driver(s) to
be used to bind the port. The valid values are normal, macvtap,
direct, baremetal, direct-physical, virtio-forwarder, smart-nic and
remote-managed, although these values will not be validated in this
API to ensure compatibility with future neutron changes or custom
implementations. What type of vNIC is actually available depends on
deployments. If not specified, the Neutron default value is used. | | MaxLength: 64
Optional: \{\}
| +| `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
Optional: \{\}
| +| `hostID` _[HostID](#hostid)_ | hostID specifies the host where the port will be bound.
Note that when the port is attached to a server, OpenStack may
rebind the port to the server's actual compute host, which may
differ from the specified hostID if no matching scheduler hint
is used. In this case the port's status will reflect the actual
binding host, not the value specified here. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| #### PortResourceStatus @@ -2518,26 +2518,26 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the human-readable name of the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `networkID` _string_ | networkID is the ID of the attached network. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
| -| `status` _string_ | status indicates the current status of the resource. | | MaxLength: 1024
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). | | | -| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 1024
| -| `deviceID` _string_ | deviceID is the ID of the device that uses this port. | | MaxLength: 1024
| -| `deviceOwner` _string_ | deviceOwner is the entity type that uses this port. | | MaxLength: 1024
| -| `allowedAddressPairs` _[AllowedAddressPairStatus](#allowedaddresspairstatus) array_ | allowedAddressPairs is a set of zero or more allowed address pair
objects each where address pair object contains an IP address and
MAC address. | | MaxItems: 128
| -| `fixedIPs` _[FixedIPStatus](#fixedipstatus) array_ | fixedIPs is a set of zero or more fixed IP objects each where fixed
IP object contains an IP address and subnet ID from which the IP
address is assigned. | | MaxItems: 128
| -| `securityGroups` _string array_ | securityGroups contains the IDs of security groups applied to the port. | | MaxItems: 64
items:MaxLength: 1024
| -| `propagateUplinkStatus` _boolean_ | propagateUplinkStatus represents the uplink status propagation of
the port. | | | -| `vnicType` _string_ | vnicType is the type of vNIC which this port is attached to. | | MaxLength: 64
| -| `portSecurityEnabled` _boolean_ | portSecurityEnabled indicates whether port security is enabled or not. | | | -| `hostID` _string_ | hostID is the ID of host where the port resides. | | MaxLength: 128
| -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | +| `name` _string_ | name is the human-readable name of the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `networkID` _string_ | networkID is the ID of the attached network. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status indicates the current status of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). | | Optional: \{\}
| +| `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 1024
Optional: \{\}
| +| `deviceID` _string_ | deviceID is the ID of the device that uses this port. | | MaxLength: 1024
Optional: \{\}
| +| `deviceOwner` _string_ | deviceOwner is the entity type that uses this port. | | MaxLength: 1024
Optional: \{\}
| +| `allowedAddressPairs` _[AllowedAddressPairStatus](#allowedaddresspairstatus) array_ | allowedAddressPairs is a set of zero or more allowed address pair
objects each where address pair object contains an IP address and
MAC address. | | MaxItems: 128
Optional: \{\}
| +| `fixedIPs` _[FixedIPStatus](#fixedipstatus) array_ | fixedIPs is a set of zero or more fixed IP objects each where fixed
IP object contains an IP address and subnet ID from which the IP
address is assigned. | | MaxItems: 128
Optional: \{\}
| +| `securityGroups` _string array_ | securityGroups contains the IDs of security groups applied to the port. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `propagateUplinkStatus` _boolean_ | propagateUplinkStatus represents the uplink status propagation of
the port. | | Optional: \{\}
| +| `vnicType` _string_ | vnicType is the type of vNIC which this port is attached to. | | MaxLength: 64
Optional: \{\}
| +| `portSecurityEnabled` _boolean_ | portSecurityEnabled indicates whether port security is enabled or not. | | Optional: \{\}
| +| `hostID` _string_ | hostID is the ID of host where the port resides. | | MaxLength: 128
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: \{\}
| #### PortSecurityState @@ -2572,11 +2572,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[PortImport](#portimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[PortResourceSpec](#portresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[PortImport](#portimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[PortResourceSpec](#portresourcespec)_ | 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: \{\}
| #### PortStatus @@ -2592,9 +2592,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[PortResourceStatus](#portresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[PortResourceStatus](#portresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Project @@ -2611,9 +2611,9 @@ Project is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Project` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[ProjectSpec](#projectspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[ProjectStatus](#projectstatus)_ | status defines the observed state of the resource. | | | +| `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` _[ProjectSpec](#projectspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ProjectStatus](#projectstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### ProjectFilter @@ -2630,11 +2630,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
| -| `tags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| -| `tagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| -| `notTags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| -| `notTagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
| +| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `tags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `tagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `notTags` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `notTagsAny` _[KeystoneTag](#keystonetag) 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: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| #### ProjectImport @@ -2653,8 +2653,8 @@ _Appears in:_ | 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
| -| `filter` _[ProjectFilter](#projectfilter)_ | 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
| +| `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` _[ProjectFilter](#projectfilter)_ | 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: \{\}
| #### ProjectResourceSpec @@ -2670,10 +2670,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
| -| `description` _string_ | description contains a free form description of the project. | | MaxLength: 65535
MinLength: 1
| -| `enabled` _boolean_ | enabled defines whether a project is enabled or not. Default is true. | | | -| `tags` _[KeystoneTag](#keystonetag) array_ | tags is list of simple strings assigned to a project.
Tags can be used to classify projects into groups. | | MaxItems: 80
MaxLength: 255
MinLength: 1
| +| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `description` _string_ | description contains a free form description of the project. | | MaxLength: 65535
MinLength: 1
Optional: \{\}
| +| `enabled` _boolean_ | enabled defines whether a project is enabled or not. Default is true. | | Optional: \{\}
| +| `tags` _[KeystoneTag](#keystonetag) array_ | tags is list of simple strings assigned to a project.
Tags can be used to classify projects into groups. | | MaxItems: 80
MaxLength: 255
MinLength: 1
Optional: \{\}
| #### ProjectResourceStatus @@ -2689,10 +2689,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the project. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 65535
| -| `enabled` _boolean_ | enabled represents whether a project is enabled or not. | | | -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 80
items:MaxLength: 1024
| +| `name` _string_ | name is a Human-readable name for the project. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 65535
Optional: \{\}
| +| `enabled` _boolean_ | enabled represents whether a project is enabled or not. | | Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 80
items:MaxLength: 1024
Optional: \{\}
| #### ProjectSpec @@ -2708,11 +2708,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[ProjectImport](#projectimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[ProjectResourceSpec](#projectresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[ProjectImport](#projectimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ProjectResourceSpec](#projectresourcespec)_ | 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: \{\}
| #### ProjectStatus @@ -2728,9 +2728,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[ProjectResourceStatus](#projectresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[ProjectResourceStatus](#projectresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Protocol @@ -2785,9 +2785,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `networkType` _string_ | networkType is the type of physical network that this
network should be mapped to. Supported values are flat, vlan, vxlan, and gre.
Valid values depend on the networking back-end. | | MaxLength: 1024
| -| `physicalNetwork` _string_ | physicalNetwork is the physical network where this network
should be implemented. The Networking API v2.0 does not provide a
way to list available physical networks. For example, the Open
vSwitch plug-in configuration file defines a symbolic name that maps
to specific bridges on each compute host. | | MaxLength: 1024
| -| `segmentationID` _integer_ | segmentationID is the ID of the isolated segment on the
physical network. The network_type attribute defines the
segmentation model. For example, if the network_type value is vlan,
this ID is a vlan identifier. If the network_type value is gre, this
ID is a gre key. | | | +| `networkType` _string_ | networkType is the type of physical network that this
network should be mapped to. Supported values are flat, vlan, vxlan, and gre.
Valid values depend on the networking back-end. | | MaxLength: 1024
Optional: \{\}
| +| `physicalNetwork` _string_ | physicalNetwork is the physical network where this network
should be implemented. The Networking API v2.0 does not provide a
way to list available physical networks. For example, the Open
vSwitch plug-in configuration file defines a symbolic name that maps
to specific bridges on each compute host. | | MaxLength: 1024
Optional: \{\}
| +| `segmentationID` _integer_ | segmentationID is the ID of the isolated segment on the
physical network. The network_type attribute defines the
segmentation model. For example, if the network_type value is vlan,
this ID is a vlan identifier. If the network_type value is gre, this
ID is a gre key. | | Optional: \{\}
| #### Role @@ -2804,9 +2804,9 @@ Role is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Role` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[RoleSpec](#rolespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[RoleStatus](#rolestatus)_ | status defines the observed state of the resource. | | | +| `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` _[RoleSpec](#rolespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[RoleStatus](#rolestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### RoleFilter @@ -2823,8 +2823,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `name` _[KeystoneName](#keystonename)_ | name of the existing resource | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### RoleImport @@ -2843,8 +2843,8 @@ _Appears in:_ | 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
| -| `filter` _[RoleFilter](#rolefilter)_ | 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
| +| `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` _[RoleFilter](#rolefilter)_ | 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: \{\}
| #### RoleResourceSpec @@ -2860,9 +2860,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `name` _[KeystoneName](#keystonename)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 64
MinLength: 1
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### RoleResourceStatus @@ -2878,9 +2878,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
| +| `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: \{\}
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| #### RoleSpec @@ -2896,11 +2896,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[RoleImport](#roleimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[RoleResourceSpec](#roleresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[RoleImport](#roleimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[RoleResourceSpec](#roleresourcespec)_ | 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: \{\}
| #### RoleStatus @@ -2916,9 +2916,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[RoleResourceStatus](#roleresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[RoleResourceStatus](#roleresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Router @@ -2935,9 +2935,9 @@ Router is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Router` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[RouterSpec](#routerspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[RouterStatus](#routerstatus)_ | status defines the observed state of the resource. | | | +| `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` _[RouterSpec](#routerspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[RouterStatus](#routerstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### RouterFilter @@ -2954,13 +2954,13 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `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
| -| `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
| -| `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
| -| `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
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
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 this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
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: \{\}
| #### RouterImport @@ -2979,8 +2979,8 @@ _Appears in:_ | 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
| -| `filter` _[RouterFilter](#routerfilter)_ | 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
| +| `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` _[RouterFilter](#routerfilter)_ | 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: \{\}
| #### RouterInterface @@ -2997,9 +2997,9 @@ RouterInterface is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `RouterInterface` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[RouterInterfaceSpec](#routerinterfacespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[RouterInterfaceStatus](#routerinterfacestatus)_ | status defines the observed state of the resource. | | | +| `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` _[RouterInterfaceSpec](#routerinterfacespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[RouterInterfaceStatus](#routerinterfacestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### RouterInterfaceSpec @@ -3015,9 +3015,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `type` _[RouterInterfaceType](#routerinterfacetype)_ | type specifies the type of the router interface. | | Enum: [Subnet]
MaxLength: 8
MinLength: 1
| -| `routerRef` _[KubernetesNameRef](#kubernetesnameref)_ | routerRef references the router to which this interface belongs. | | MaxLength: 253
MinLength: 1
| -| `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef references the subnet the router interface is created on. | | MaxLength: 253
MinLength: 1
| +| `type` _[RouterInterfaceType](#routerinterfacetype)_ | type specifies the type of the router interface. | | Enum: [Subnet]
MaxLength: 8
MinLength: 1
Required: \{\}
| +| `routerRef` _[KubernetesNameRef](#kubernetesnameref)_ | routerRef references the router to which this interface belongs. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef references the subnet the router interface is created on. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### RouterInterfaceStatus @@ -3033,8 +3033,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the port created for the router interface | | MaxLength: 1024
| +| `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 port created for the router interface | | MaxLength: 1024
Optional: \{\}
| #### RouterInterfaceType @@ -3069,14 +3069,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name is a human-readable name of the router. If not set, the
object's name will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the router. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `adminStateUp` _boolean_ | adminStateUp represents the administrative state of the resource,
which is up (true) or down (false). Default is true. | | | -| `externalGateways` _[ExternalGateway](#externalgateway) array_ | externalGateways is a list of external gateways for the router.
Multiple gateways are not currently supported by ORC. | | MaxItems: 1
| -| `distributed` _boolean_ | distributed indicates whether the router is distributed or not. It
is available when dvr extension is enabled. | | | -| `availabilityZoneHints` _[AvailabilityZoneHint](#availabilityzonehint) array_ | availabilityZoneHints is the availability zone candidate for the router. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| +| `name` _[OpenStackName](#openstackname)_ | name is a human-readable name of the router. If not set, the
object's name will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the router. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp represents the administrative state of the resource,
which is up (true) or down (false). Default is true. | | Optional: \{\}
| +| `externalGateways` _[ExternalGateway](#externalgateway) array_ | externalGateways is a list of external gateways for the router.
Multiple gateways are not currently supported by ORC. | | MaxItems: 1
Optional: \{\}
| +| `distributed` _boolean_ | distributed indicates whether the router is distributed or not. It
is available when dvr extension is enabled. | | Optional: \{\}
| +| `availabilityZoneHints` _[AvailabilityZoneHint](#availabilityzonehint) array_ | availabilityZoneHints is the availability zone candidate for the router. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### RouterResourceStatus @@ -3092,14 +3092,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the human-readable name of the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
| -| `status` _string_ | status indicates the current status of the resource. | | MaxLength: 1024
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the router,
which is up (true) or down (false). | | | -| `externalGateways` _[ExternalGatewayStatus](#externalgatewaystatus) array_ | externalGateways is a list of external gateways for the router. | | MaxItems: 32
| -| `availabilityZoneHints` _string array_ | availabilityZoneHints is the availability zone candidate for the
router. | | MaxItems: 64
items:MaxLength: 1024
| +| `name` _string_ | name is the human-readable name of 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 project owner of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status indicates the current status of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the router,
which is up (true) or down (false). | | Optional: \{\}
| +| `externalGateways` _[ExternalGatewayStatus](#externalgatewaystatus) array_ | externalGateways is a list of external gateways for the router. | | MaxItems: 32
Optional: \{\}
| +| `availabilityZoneHints` _string array_ | availabilityZoneHints is the availability zone candidate for the
router. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| #### RouterSpec @@ -3115,11 +3115,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[RouterImport](#routerimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[RouterResourceSpec](#routerresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[RouterImport](#routerimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[RouterResourceSpec](#routerresourcespec)_ | 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: \{\}
| #### RouterStatus @@ -3135,9 +3135,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[RouterResourceStatus](#routerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[RouterResourceStatus](#routerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### RuleDirection @@ -3168,9 +3168,9 @@ SecurityGroup is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `SecurityGroup` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[SecurityGroupSpec](#securitygroupspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[SecurityGroupStatus](#securitygroupstatus)_ | status defines the observed state of the resource. | | | +| `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` _[SecurityGroupSpec](#securitygroupspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[SecurityGroupStatus](#securitygroupstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### SecurityGroupFilter @@ -3187,13 +3187,13 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `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
| -| `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
| -| `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
| -| `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
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
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 this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
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: \{\}
| #### SecurityGroupImport @@ -3212,8 +3212,8 @@ _Appears in:_ | 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
| -| `filter` _[SecurityGroupFilter](#securitygroupfilter)_ | 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
| +| `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` _[SecurityGroupFilter](#securitygroupfilter)_ | 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: \{\}
| #### SecurityGroupResourceSpec @@ -3229,12 +3229,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the security group. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `stateful` _boolean_ | stateful indicates if the security group is stateful or stateless. | | | -| `rules` _[SecurityGroupRule](#securitygrouprule) array_ | rules is a list of security group rules belonging to this SG. | | MaxItems: 256
MinProperties: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| +| `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` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the security group. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `stateful` _boolean_ | stateful indicates if the security group is stateful or stateless. | | Optional: \{\}
| +| `rules` _[SecurityGroupRule](#securitygrouprule) array_ | rules is a list of security group rules belonging to this SG. | | MaxItems: 256
MinProperties: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### SecurityGroupResourceStatus @@ -3250,15 +3250,15 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the security group. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the security group. | | MaxLength: 1024
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| -| `stateful` _boolean_ | stateful indicates if the security group is stateful or stateless. | | | -| `rules` _[SecurityGroupRuleStatus](#securitygrouprulestatus) array_ | rules is a list of security group rules belonging to this SG. | | MaxItems: 256
| -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | +| `name` _string_ | name is a Human-readable name for the security group. 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 project owner of the security group. | | MaxLength: 1024
Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `stateful` _boolean_ | stateful indicates if the security group is stateful or stateless. | | Optional: \{\}
| +| `rules` _[SecurityGroupRuleStatus](#securitygrouprulestatus) array_ | rules is a list of security group rules belonging to this SG. | | MaxItems: 256
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: \{\}
| #### SecurityGroupRule @@ -3275,12 +3275,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `direction` _[RuleDirection](#ruledirection)_ | direction represents the direction in which the security group rule
is applied. Can be ingress or egress. | | Enum: [ingress egress]
| -| `remoteIPPrefix` _[CIDR](#cidr)_ | remoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6) | | Format: cidr
MaxLength: 49
MinLength: 1
| -| `protocol` _[Protocol](#protocol)_ | protocol is the IP protocol is represented by a string | | Enum: [ah dccp egp esp gre icmp icmpv6 igmp ipip ipv6-encap ipv6-frag ipv6-icmp ipv6-nonxt ipv6-opts ipv6-route ospf pgm rsvp sctp tcp udp udplite vrrp]
| -| `ethertype` _[Ethertype](#ethertype)_ | ethertype must be IPv4 or IPv6, and addresses represented in CIDR
must match the ingress or egress rules. | | Enum: [IPv4 IPv6]
| -| `portRange` _[PortRangeSpec](#portrangespec)_ | portRange sets the minimum and maximum ports range that the security group rule
matches. If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than
or equal to the PortRange.Max attribute value.
If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max
should be an ICMP type | | | +| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `direction` _[RuleDirection](#ruledirection)_ | direction represents the direction in which the security group rule
is applied. Can be ingress or egress. | | Enum: [ingress egress]
Optional: \{\}
| +| `remoteIPPrefix` _[CIDR](#cidr)_ | remoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6) | | Format: cidr
MaxLength: 49
MinLength: 1
Optional: \{\}
| +| `protocol` _[Protocol](#protocol)_ | protocol is the IP protocol is represented by a string | | Enum: [ah dccp egp esp gre icmp icmpv6 igmp ipip ipv6-encap ipv6-frag ipv6-icmp ipv6-nonxt ipv6-opts ipv6-route ospf pgm rsvp sctp tcp udp udplite vrrp]
Optional: \{\}
| +| `ethertype` _[Ethertype](#ethertype)_ | ethertype must be IPv4 or IPv6, and addresses represented in CIDR
must match the ingress or egress rules. | | Enum: [IPv4 IPv6]
Required: \{\}
| +| `portRange` _[PortRangeSpec](#portrangespec)_ | portRange sets the minimum and maximum ports range that the security group rule
matches. If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than
or equal to the PortRange.Max attribute value.
If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max
should be an ICMP type | | Optional: \{\}
| #### SecurityGroupRuleStatus @@ -3296,14 +3296,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `id` _string_ | id is the ID of the security group rule. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `direction` _string_ | direction represents the direction in which the security group rule
is applied. Can be ingress or egress. | | MaxLength: 1024
| -| `remoteGroupID` _string_ | remoteGroupID is the remote group UUID to associate with this security group rule
RemoteGroupID | | MaxLength: 1024
| -| `remoteIPPrefix` _string_ | remoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6) | | MaxLength: 1024
| -| `protocol` _string_ | protocol is the IP protocol can be represented by a string, an
integer, or null | | MaxLength: 1024
| -| `ethertype` _string_ | ethertype must be IPv4 or IPv6, and addresses represented in CIDR
must match the ingress or egress rules. | | MaxLength: 1024
| -| `portRange` _[PortRangeStatus](#portrangestatus)_ | portRange sets the minimum and maximum ports range that the security group rule
matches. If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than
or equal to the PortRange.Max attribute value.
If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max
should be an ICMP type | | | +| `id` _string_ | id is the ID of the security group rule. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `direction` _string_ | direction represents the direction in which the security group rule
is applied. Can be ingress or egress. | | MaxLength: 1024
Optional: \{\}
| +| `remoteGroupID` _string_ | remoteGroupID is the remote group UUID to associate with this security group rule
RemoteGroupID | | MaxLength: 1024
Optional: \{\}
| +| `remoteIPPrefix` _string_ | remoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6) | | MaxLength: 1024
Optional: \{\}
| +| `protocol` _string_ | protocol is the IP protocol can be represented by a string, an
integer, or null | | MaxLength: 1024
Optional: \{\}
| +| `ethertype` _string_ | ethertype must be IPv4 or IPv6, and addresses represented in CIDR
must match the ingress or egress rules. | | MaxLength: 1024
Optional: \{\}
| +| `portRange` _[PortRangeStatus](#portrangestatus)_ | portRange sets the minimum and maximum ports range that the security group rule
matches. If the protocol is [tcp, udp, dccp sctp,udplite] PortRange.Min must be less than
or equal to the PortRange.Max attribute value.
If the protocol is ICMP, this PortRamge.Min must be an ICMP code and PortRange.Max
should be an ICMP type | | Optional: \{\}
| #### SecurityGroupSpec @@ -3319,11 +3319,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[SecurityGroupImport](#securitygroupimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[SecurityGroupResourceSpec](#securitygroupresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[SecurityGroupImport](#securitygroupimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[SecurityGroupResourceSpec](#securitygroupresourcespec)_ | 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: \{\}
| #### SecurityGroupStatus @@ -3339,9 +3339,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[SecurityGroupResourceStatus](#securitygroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[SecurityGroupResourceStatus](#securitygroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Server @@ -3358,9 +3358,9 @@ Server is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Server` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[ServerSpec](#serverspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[ServerStatus](#serverstatus)_ | status defines the observed state of the resource. | | | +| `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` _[ServerSpec](#serverspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ServerStatus](#serverstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### ServerFilter @@ -3377,12 +3377,12 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `availabilityZone` _string_ | availabilityZone is the availability zone of the existing resource | | MaxLength: 255
| -| `tags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| -| `tagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| -| `notTags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| -| `notTagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `availabilityZone` _string_ | availabilityZone is the availability zone of the existing resource | | MaxLength: 255
Optional: \{\}
| +| `tags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `tagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `notTags` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `notTagsAny` _[ServerTag](#servertag) 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: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| #### ServerGroup @@ -3399,9 +3399,9 @@ ServerGroup is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `ServerGroup` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[ServerGroupSpec](#servergroupspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[ServerGroupStatus](#servergroupstatus)_ | status defines the observed state of the resource. | | | +| `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` _[ServerGroupSpec](#servergroupspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ServerGroupStatus](#servergroupstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### ServerGroupFilter @@ -3418,7 +3418,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| #### ServerGroupImport @@ -3437,8 +3437,8 @@ _Appears in:_ | 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
| -| `filter` _[ServerGroupFilter](#servergroupfilter)_ | 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
| +| `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` _[ServerGroupFilter](#servergroupfilter)_ | 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: \{\}
| #### ServerGroupPolicy @@ -3474,9 +3474,9 @@ _Appears in:_ | 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: `^[^,]+$`
| -| `policy` _[ServerGroupPolicy](#servergrouppolicy)_ | policy is the policy to use for the server group. | | Enum: [affinity anti-affinity soft-affinity soft-anti-affinity]
| -| `rules` _[ServerGroupRules](#servergrouprules)_ | rules is the rules to use for the server group. | | | +| `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: \{\}
| +| `policy` _[ServerGroupPolicy](#servergrouppolicy)_ | policy is the policy to use for the server group. | | Enum: [affinity anti-affinity soft-affinity soft-anti-affinity]
Required: \{\}
| +| `rules` _[ServerGroupRules](#servergrouprules)_ | rules is the rules to use for the server group. | | Optional: \{\}
| #### ServerGroupResourceStatus @@ -3492,11 +3492,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the servergroup. Might not be unique. | | MaxLength: 1024
| -| `policy` _string_ | policy is the policy of the servergroup. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
| -| `userID` _string_ | userID of the server group. | | MaxLength: 1024
| -| `rules` _[ServerGroupRulesStatus](#servergrouprulesstatus)_ | rules is the rules of the server group. | | | +| `name` _string_ | name is a Human-readable name for the servergroup. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `policy` _string_ | policy is the policy of the servergroup. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the project owner of the resource. | | MaxLength: 1024
Optional: \{\}
| +| `userID` _string_ | userID of the server group. | | MaxLength: 1024
Optional: \{\}
| +| `rules` _[ServerGroupRulesStatus](#servergrouprulesstatus)_ | rules is the rules of the server group. | | Optional: \{\}
| #### ServerGroupRules @@ -3512,7 +3512,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `maxServerPerHost` _integer_ | maxServerPerHost specifies how many servers can reside on a single compute host.
It can be used only with the "anti-affinity" policy. | | | +| `maxServerPerHost` _integer_ | maxServerPerHost specifies how many servers can reside on a single compute host.
It can be used only with the "anti-affinity" policy. | | Optional: \{\}
| #### ServerGroupRulesStatus @@ -3528,7 +3528,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `maxServerPerHost` _integer_ | maxServerPerHost specifies how many servers can reside on a single compute host.
It can be used only with the "anti-affinity" policy. | | | +| `maxServerPerHost` _integer_ | maxServerPerHost specifies how many servers can reside on a single compute host.
It can be used only with the "anti-affinity" policy. | | Optional: \{\}
| #### ServerGroupSpec @@ -3544,11 +3544,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[ServerGroupImport](#servergroupimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[ServerGroupResourceSpec](#servergroupresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[ServerGroupImport](#servergroupimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ServerGroupResourceSpec](#servergroupresourcespec)_ | 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: \{\}
| #### ServerGroupStatus @@ -3564,9 +3564,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[ServerGroupResourceStatus](#servergroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[ServerGroupResourceStatus](#servergroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### ServerImport @@ -3585,8 +3585,8 @@ _Appears in:_ | 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
| -| `filter` _[ServerFilter](#serverfilter)_ | 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
| +| `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` _[ServerFilter](#serverfilter)_ | 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: \{\}
| #### ServerInterfaceFixedIP @@ -3602,8 +3602,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `ipAddress` _string_ | ipAddress is the IP address assigned to the port. | | MaxLength: 1024
| -| `subnetID` _string_ | subnetID is the ID of the subnet from which the IP address is allocated. | | MaxLength: 1024
| +| `ipAddress` _string_ | ipAddress is the IP address assigned to the port. | | MaxLength: 1024
Optional: \{\}
| +| `subnetID` _string_ | subnetID is the ID of the subnet from which the IP address is allocated. | | MaxLength: 1024
Optional: \{\}
| #### ServerInterfaceStatus @@ -3619,11 +3619,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `portID` _string_ | portID is the ID of a port attached to the server. | | MaxLength: 1024
| -| `netID` _string_ | netID is the ID of the network to which the interface is attached. | | MaxLength: 1024
| -| `macAddr` _string_ | macAddr is the MAC address of the interface. | | MaxLength: 1024
| -| `portState` _string_ | portState is the state of the port (e.g., ACTIVE, DOWN). | | MaxLength: 1024
| -| `fixedIPs` _[ServerInterfaceFixedIP](#serverinterfacefixedip) array_ | fixedIPs is the list of fixed IP addresses assigned to the interface. | | MaxItems: 32
| +| `portID` _string_ | portID is the ID of a port attached to the server. | | MaxLength: 1024
Optional: \{\}
| +| `netID` _string_ | netID is the ID of the network to which the interface is attached. | | MaxLength: 1024
Optional: \{\}
| +| `macAddr` _string_ | macAddr is the MAC address of the interface. | | MaxLength: 1024
Optional: \{\}
| +| `portState` _string_ | portState is the state of the port (e.g., ACTIVE, DOWN). | | MaxLength: 1024
Optional: \{\}
| +| `fixedIPs` _[ServerInterfaceFixedIP](#serverinterfacefixedip) array_ | fixedIPs is the list of fixed IP addresses assigned to the interface. | | MaxItems: 32
Optional: \{\}
| #### ServerMetadata @@ -3639,8 +3639,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `key` _string_ | key is the metadata key. | | MaxLength: 255
MinLength: 1
| -| `value` _string_ | value is the metadata value. | | MaxLength: 255
MinLength: 1
| +| `key` _string_ | key is the metadata key. | | MaxLength: 255
MinLength: 1
Required: \{\}
| +| `value` _string_ | value is the metadata value. | | MaxLength: 255
MinLength: 1
Required: \{\}
| #### ServerMetadataStatus @@ -3656,8 +3656,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `key` _string_ | key is the metadata key. | | MaxLength: 255
| -| `value` _string_ | value is the metadata value. | | MaxLength: 255
| +| `key` _string_ | key is the metadata key. | | MaxLength: 255
Optional: \{\}
| +| `value` _string_ | value is the metadata value. | | MaxLength: 255
Optional: \{\}
| #### ServerPortSpec @@ -3675,7 +3675,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to a Port object. Server creation will wait for
this port to be created and available. | | MaxLength: 253
MinLength: 1
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to a Port object. Server creation will wait for
this port to be created and available. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### ServerResourceSpec @@ -3691,18 +3691,18 @@ _Appears in:_ | 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: `^[^,]+$`
| -| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef references the image to use for the server instance.
NOTE: This is not required in case of boot from volume. | | MaxLength: 253
MinLength: 1
| -| `flavorRef` _[KubernetesNameRef](#kubernetesnameref)_ | flavorRef references the flavor to use for the server instance. | | MaxLength: 253
MinLength: 1
| -| `userData` _[UserDataSpec](#userdataspec)_ | userData specifies data which will be made available to the server at
boot time, either via the metadata service or a config drive. It is
typically read by a configuration service such as cloud-init or ignition. | | MaxProperties: 1
MinProperties: 1
| -| `ports` _[ServerPortSpec](#serverportspec) array_ | ports defines a list of ports which will be attached to the server. | | MaxItems: 64
MaxProperties: 1
MinProperties: 1
| -| `volumes` _[ServerVolumeSpec](#servervolumespec) array_ | volumes is a list of volumes attached to the server. | | MaxItems: 64
MinProperties: 1
| -| `serverGroupRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverGroupRef is a reference to a ServerGroup object. The server
will be created in the server group. | | MaxLength: 253
MinLength: 1
| -| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the server. | | MaxLength: 255
| -| `keypairRef` _[KubernetesNameRef](#kubernetesnameref)_ | keypairRef is a reference to a KeyPair object. The server will be
created with this keypair for SSH access. | | MaxLength: 253
MinLength: 1
| -| `tags` _[ServerTag](#servertag) array_ | tags is a list of tags which will be applied to the server. | | MaxItems: 50
MaxLength: 80
MinLength: 1
| -| `metadata` _[ServerMetadata](#servermetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
| -| `configDrive` _boolean_ | configDrive specifies whether to attach a config drive to the server.
When true, configuration data will be available via a special drive
instead of the metadata service. | | | +| `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: \{\}
| +| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef references the image to use for the server instance.
NOTE: This is not required in case of boot from volume. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `flavorRef` _[KubernetesNameRef](#kubernetesnameref)_ | flavorRef references the flavor to use for the server instance. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `userData` _[UserDataSpec](#userdataspec)_ | userData specifies data which will be made available to the server at
boot time, either via the metadata service or a config drive. It is
typically read by a configuration service such as cloud-init or ignition. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `ports` _[ServerPortSpec](#serverportspec) array_ | ports defines a list of ports which will be attached to the server. | | MaxItems: 64
MaxProperties: 1
MinProperties: 1
Required: \{\}
| +| `volumes` _[ServerVolumeSpec](#servervolumespec) array_ | volumes is a list of volumes attached to the server. | | MaxItems: 64
MinProperties: 1
Optional: \{\}
| +| `serverGroupRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverGroupRef is a reference to a ServerGroup object. The server
will be created in the server group. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the server. | | MaxLength: 255
Optional: \{\}
| +| `keypairRef` _[KubernetesNameRef](#kubernetesnameref)_ | keypairRef is a reference to a KeyPair object. The server will be
created with this keypair for SSH access. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `tags` _[ServerTag](#servertag) array_ | tags is a list of tags which will be applied to the server. | | MaxItems: 50
MaxLength: 80
MinLength: 1
Optional: \{\}
| +| `metadata` _[ServerMetadata](#servermetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
Optional: \{\}
| +| `configDrive` _boolean_ | configDrive specifies whether to attach a config drive to the server.
When true, configuration data will be available via a special drive
instead of the metadata service. | | Optional: \{\}
| #### ServerResourceStatus @@ -3718,17 +3718,17 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the human-readable name of the resource. Might not be unique. | | MaxLength: 1024
| -| `hostID` _string_ | hostID is the host where the server is located in the cloud. | | MaxLength: 1024
| -| `status` _string_ | status contains the current operational status of the server,
such as IN_PROGRESS or ACTIVE. | | MaxLength: 1024
| -| `imageID` _string_ | imageID indicates the OS image used to deploy the server. | | MaxLength: 1024
| -| `availabilityZone` _string_ | availabilityZone is the availability zone where the server is located. | | MaxLength: 1024
| -| `serverGroups` _string array_ | serverGroups is a slice of strings containing the UUIDs of the
server groups to which the server belongs. Currently this can
contain at most one entry. | | MaxItems: 32
items:MaxLength: 1024
| -| `volumes` _[ServerVolumeStatus](#servervolumestatus) array_ | volumes contains the volumes attached to the server. | | MaxItems: 64
| -| `interfaces` _[ServerInterfaceStatus](#serverinterfacestatus) array_ | interfaces contains the list of interfaces attached to the server. | | MaxItems: 64
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 50
items:MaxLength: 1024
| -| `metadata` _[ServerMetadataStatus](#servermetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
| -| `configDrive` _boolean_ | configDrive indicates whether the server was booted with a config drive. | | | +| `name` _string_ | name is the human-readable name of the resource. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `hostID` _string_ | hostID is the host where the server is located in the cloud. | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status contains the current operational status of the server,
such as IN_PROGRESS or ACTIVE. | | MaxLength: 1024
Optional: \{\}
| +| `imageID` _string_ | imageID indicates the OS image used to deploy the server. | | MaxLength: 1024
Optional: \{\}
| +| `availabilityZone` _string_ | availabilityZone is the availability zone where the server is located. | | MaxLength: 1024
Optional: \{\}
| +| `serverGroups` _string array_ | serverGroups is a slice of strings containing the UUIDs of the
server groups to which the server belongs. Currently this can
contain at most one entry. | | MaxItems: 32
items:MaxLength: 1024
Optional: \{\}
| +| `volumes` _[ServerVolumeStatus](#servervolumestatus) array_ | volumes contains the volumes attached to the server. | | MaxItems: 64
Optional: \{\}
| +| `interfaces` _[ServerInterfaceStatus](#serverinterfacestatus) array_ | interfaces contains the list of interfaces attached to the server. | | MaxItems: 64
Optional: \{\}
| +| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 50
items:MaxLength: 1024
Optional: \{\}
| +| `metadata` _[ServerMetadataStatus](#servermetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
Optional: \{\}
| +| `configDrive` _boolean_ | configDrive indicates whether the server was booted with a config drive. | | Optional: \{\}
| #### ServerSpec @@ -3744,11 +3744,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[ServerImport](#serverimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[ServerResourceSpec](#serverresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[ServerImport](#serverimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ServerResourceSpec](#serverresourcespec)_ | 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: \{\}
| #### ServerStatus @@ -3764,9 +3764,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[ServerResourceStatus](#serverresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[ServerResourceStatus](#serverresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### ServerTag @@ -3800,8 +3800,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `volumeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeRef is a reference to a Volume object. Server creation will wait for
this volume to be created and available. | | MaxLength: 253
MinLength: 1
| -| `device` _string_ | device is the name of the device, such as `/dev/vdb`.
Omit for auto-assignment | | MaxLength: 255
| +| `volumeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeRef is a reference to a Volume object. Server creation will wait for
this volume to be created and available. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `device` _string_ | device is the name of the device, such as `/dev/vdb`.
Omit for auto-assignment | | MaxLength: 255
Optional: \{\}
| #### ServerVolumeStatus @@ -3817,7 +3817,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `id` _string_ | id is the ID of a volume attached to the server. | | MaxLength: 1024
| +| `id` _string_ | id is the ID of a volume attached to the server. | | MaxLength: 1024
Optional: \{\}
| #### Service @@ -3834,9 +3834,9 @@ Service is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Service` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[ServiceSpec](#servicespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[ServiceStatus](#servicestatus)_ | status defines the observed state of the resource. | | | +| `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` _[ServiceSpec](#servicespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ServiceStatus](#servicestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### ServiceFilter @@ -3853,8 +3853,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `type` _string_ | type of the existing resource | | MaxLength: 255
MinLength: 1
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `type` _string_ | type of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| #### ServiceImport @@ -3873,8 +3873,8 @@ _Appears in:_ | 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
| -| `filter` _[ServiceFilter](#servicefilter)_ | 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
| +| `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` _[ServiceFilter](#servicefilter)_ | 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: \{\}
| #### ServiceResourceSpec @@ -3890,10 +3890,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name indicates the name of service. If not specified, the name of the ORC
resource will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description indicates the description of service. | | MaxLength: 255
MinLength: 1
| -| `type` _string_ | type indicates which resource the service is responsible for. | | MaxLength: 255
MinLength: 1
| -| `enabled` _boolean_ | enabled indicates whether the service is enabled or not. | true | | +| `name` _[OpenStackName](#openstackname)_ | name indicates the name of service. If not specified, the name of the ORC
resource will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description indicates the description of service. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `type` _string_ | type indicates which resource the service is responsible for. | | MaxLength: 255
MinLength: 1
Required: \{\}
| +| `enabled` _boolean_ | enabled indicates whether the service is enabled or not. | true | Optional: \{\}
| #### ServiceResourceStatus @@ -3909,10 +3909,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name indicates the name of service. | | MaxLength: 255
| -| `description` _string_ | description indicates the description of service. | | MaxLength: 255
| -| `type` _string_ | type indicates which resource the service is responsible for. | | MaxLength: 255
| -| `enabled` _boolean_ | enabled indicates whether the service is enabled or not. | | | +| `name` _string_ | name indicates the name of service. | | MaxLength: 255
Optional: \{\}
| +| `description` _string_ | description indicates the description of service. | | MaxLength: 255
Optional: \{\}
| +| `type` _string_ | type indicates which resource the service is responsible for. | | MaxLength: 255
Optional: \{\}
| +| `enabled` _boolean_ | enabled indicates whether the service is enabled or not. | | Optional: \{\}
| #### ServiceSpec @@ -3928,11 +3928,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[ServiceImport](#serviceimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[ServiceResourceSpec](#serviceresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[ServiceImport](#serviceimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ServiceResourceSpec](#serviceresourcespec)_ | 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: \{\}
| #### ServiceStatus @@ -3948,9 +3948,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[ServiceResourceStatus](#serviceresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[ServiceResourceStatus](#serviceresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Subnet @@ -3967,9 +3967,9 @@ Subnet is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Subnet` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[SubnetSpec](#subnetspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[SubnetStatus](#subnetstatus)_ | status defines the observed state of the resource. | | | +| `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` _[SubnetSpec](#subnetspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[SubnetStatus](#subnetstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### SubnetFilter @@ -3986,18 +3986,18 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion of the existing resource | | Enum: [4 6]
| -| `gatewayIP` _[IPvAny](#ipvany)_ | gatewayIP is the IP address of the gateway of the existing resource | | MaxLength: 45
MinLength: 1
| -| `cidr` _[CIDR](#cidr)_ | cidr of the existing resource | | Format: cidr
MaxLength: 49
MinLength: 1
| -| `ipv6` _[IPv6Options](#ipv6options)_ | ipv6 options of the existing resource | | MinProperties: 1
| -| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this subnet is associated with. | | MaxLength: 253
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| -| `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
| -| `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
| -| `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
| -| `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
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion of the existing resource | | Enum: [4 6]
Optional: \{\}
| +| `gatewayIP` _[IPvAny](#ipvany)_ | gatewayIP is the IP address of the gateway of the existing resource | | MaxLength: 45
MinLength: 1
Optional: \{\}
| +| `cidr` _[CIDR](#cidr)_ | cidr of the existing resource | | Format: cidr
MaxLength: 49
MinLength: 1
Optional: \{\}
| +| `ipv6` _[IPv6Options](#ipv6options)_ | ipv6 options of the existing resource | | MinProperties: 1
Optional: \{\}
| +| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this subnet is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
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: \{\}
| #### SubnetGateway @@ -4013,8 +4013,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `type` _[SubnetGatewayType](#subnetgatewaytype)_ | type specifies how the default gateway will be created. `Automatic`
specifies that neutron will automatically add a default gateway. This is
also the default if no Gateway is specified. `None` specifies that the
subnet will not have a default gateway. `IP` specifies that the subnet
will use a specific address as the default gateway, which must be
specified in `IP`. | | Enum: [None Automatic IP]
| -| `ip` _[IPvAny](#ipvany)_ | ip is the IP address of the default gateway, which must be specified if
Type is `IP`. It must be a valid IP address, either IPv4 or IPv6,
matching the IPVersion in SubnetResourceSpec. | | MaxLength: 45
MinLength: 1
| +| `type` _[SubnetGatewayType](#subnetgatewaytype)_ | type specifies how the default gateway will be created. `Automatic`
specifies that neutron will automatically add a default gateway. This is
also the default if no Gateway is specified. `None` specifies that the
subnet will not have a default gateway. `IP` specifies that the subnet
will use a specific address as the default gateway, which must be
specified in `IP`. | | Enum: [None Automatic IP]
Required: \{\}
| +| `ip` _[IPvAny](#ipvany)_ | ip is the IP address of the default gateway, which must be specified if
Type is `IP`. It must be a valid IP address, either IPv4 or IPv6,
matching the IPVersion in SubnetResourceSpec. | | MaxLength: 45
MinLength: 1
Optional: \{\}
| #### SubnetGatewayType @@ -4046,8 +4046,8 @@ _Appears in:_ | 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
| -| `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
| +| `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` _[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 @@ -4063,21 +4063,21 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name is a human-readable name of the subnet. If not set, the object's name will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this subnet is associated with. | | MaxLength: 253
MinLength: 1
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the subnet. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| -| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP version for the subnet. | | Enum: [4 6]
| -| `cidr` _[CIDR](#cidr)_ | cidr is the address CIDR of the subnet. It must match the IP version specified in IPVersion. | | Format: cidr
MaxLength: 49
MinLength: 1
| -| `allocationPools` _[AllocationPool](#allocationpool) array_ | allocationPools are IP Address pools that will be available for DHCP. IP
addresses must be in CIDR. | | MaxItems: 32
| -| `gateway` _[SubnetGateway](#subnetgateway)_ | gateway specifies the default gateway of the subnet. If not specified,
neutron will add one automatically. To disable this behaviour, specify a
gateway with a type of None. | | | -| `enableDHCP` _boolean_ | enableDHCP will either enable to disable the DHCP service. | | | -| `dnsNameservers` _[IPvAny](#ipvany) array_ | dnsNameservers are the nameservers to be set via DHCP. | | MaxItems: 16
MaxLength: 45
MinLength: 1
| -| `dnsPublishFixedIP` _boolean_ | dnsPublishFixedIP will either enable or disable the publication of
fixed IPs to the DNS. Defaults to false. | | | -| `hostRoutes` _[HostRoute](#hostroute) array_ | hostRoutes are any static host routes to be set via DHCP. | | MaxItems: 256
| -| `ipv6` _[IPv6Options](#ipv6options)_ | ipv6 contains IPv6-specific options. It may only be set if IPVersion is 6. | | MinProperties: 1
| -| `routerRef` _[KubernetesNameRef](#kubernetesnameref)_ | routerRef specifies a router to attach the subnet to | | MaxLength: 253
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| +| `name` _[OpenStackName](#openstackname)_ | name is a human-readable name of the subnet. If not set, the object's name will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this subnet is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of tags which will be applied to the subnet. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `ipVersion` _[IPVersion](#ipversion)_ | ipVersion is the IP version for the subnet. | | Enum: [4 6]
Required: \{\}
| +| `cidr` _[CIDR](#cidr)_ | cidr is the address CIDR of the subnet. It must match the IP version specified in IPVersion. | | Format: cidr
MaxLength: 49
MinLength: 1
Required: \{\}
| +| `allocationPools` _[AllocationPool](#allocationpool) array_ | allocationPools are IP Address pools that will be available for DHCP. IP
addresses must be in CIDR. | | MaxItems: 32
Optional: \{\}
| +| `gateway` _[SubnetGateway](#subnetgateway)_ | gateway specifies the default gateway of the subnet. If not specified,
neutron will add one automatically. To disable this behaviour, specify a
gateway with a type of None. | | Optional: \{\}
| +| `enableDHCP` _boolean_ | enableDHCP will either enable to disable the DHCP service. | | Optional: \{\}
| +| `dnsNameservers` _[IPvAny](#ipvany) array_ | dnsNameservers are the nameservers to be set via DHCP. | | MaxItems: 16
MaxLength: 45
MinLength: 1
Optional: \{\}
| +| `dnsPublishFixedIP` _boolean_ | dnsPublishFixedIP will either enable or disable the publication of
fixed IPs to the DNS. Defaults to false. | | Optional: \{\}
| +| `hostRoutes` _[HostRoute](#hostroute) array_ | hostRoutes are any static host routes to be set via DHCP. | | MaxItems: 256
Optional: \{\}
| +| `ipv6` _[IPv6Options](#ipv6options)_ | ipv6 contains IPv6-specific options. It may only be set if IPVersion is 6. | | MinProperties: 1
Optional: \{\}
| +| `routerRef` _[KubernetesNameRef](#kubernetesnameref)_ | routerRef specifies a router to attach the subnet to | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### SubnetResourceStatus @@ -4093,25 +4093,25 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the human-readable name of the subnet. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `ipVersion` _integer_ | ipVersion specifies IP version, either `4' or `6'. | | | -| `cidr` _string_ | cidr representing IP range for this subnet, based on IP version. | | MaxLength: 1024
| -| `gatewayIP` _string_ | gatewayIP is the default gateway used by devices in this subnet, if any. | | MaxLength: 1024
| -| `dnsNameservers` _string array_ | dnsNameservers is a list of name servers used by hosts in this subnet. | | MaxItems: 16
items:MaxLength: 1024
| -| `dnsPublishFixedIP` _boolean_ | dnsPublishFixedIP specifies whether the fixed IP addresses are published to the DNS. | | | -| `allocationPools` _[AllocationPoolStatus](#allocationpoolstatus) array_ | allocationPools is a list of sub-ranges within CIDR available for dynamic
allocation to ports. | | MaxItems: 32
| -| `hostRoutes` _[HostRouteStatus](#hostroutestatus) array_ | hostRoutes is a list of routes that should be used by devices with IPs
from this subnet (not including local subnet route). | | MaxItems: 256
| -| `enableDHCP` _boolean_ | enableDHCP specifies whether DHCP is enabled for this subnet or not. | | | -| `networkID` _string_ | networkID is the ID of the network to which the subnet belongs. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the project owner of the subnet. | | MaxLength: 1024
| -| `ipv6AddressMode` _string_ | ipv6AddressMode specifies mechanisms for assigning IPv6 IP addresses. | | MaxLength: 1024
| -| `ipv6RAMode` _string_ | ipv6RAMode is the IPv6 router advertisement mode. It specifies
whether the networking service should transmit ICMPv6 packets. | | MaxLength: 1024
| -| `subnetPoolID` _string_ | subnetPoolID is the id of the subnet pool associated with the subnet. | | MaxLength: 1024
| -| `tags` _string array_ | tags optionally set via extensions/attributestags | | MaxItems: 64
items:MaxLength: 1024
| -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | +| `name` _string_ | name is the human-readable name of the subnet. Might not be unique. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `ipVersion` _integer_ | ipVersion specifies IP version, either `4' or `6'. | | Optional: \{\}
| +| `cidr` _string_ | cidr representing IP range for this subnet, based on IP version. | | MaxLength: 1024
Optional: \{\}
| +| `gatewayIP` _string_ | gatewayIP is the default gateway used by devices in this subnet, if any. | | MaxLength: 1024
Optional: \{\}
| +| `dnsNameservers` _string array_ | dnsNameservers is a list of name servers used by hosts in this subnet. | | MaxItems: 16
items:MaxLength: 1024
Optional: \{\}
| +| `dnsPublishFixedIP` _boolean_ | dnsPublishFixedIP specifies whether the fixed IP addresses are published to the DNS. | | Optional: \{\}
| +| `allocationPools` _[AllocationPoolStatus](#allocationpoolstatus) array_ | allocationPools is a list of sub-ranges within CIDR available for dynamic
allocation to ports. | | MaxItems: 32
Optional: \{\}
| +| `hostRoutes` _[HostRouteStatus](#hostroutestatus) array_ | hostRoutes is a list of routes that should be used by devices with IPs
from this subnet (not including local subnet route). | | MaxItems: 256
Optional: \{\}
| +| `enableDHCP` _boolean_ | enableDHCP specifies whether DHCP is enabled for this subnet or not. | | Optional: \{\}
| +| `networkID` _string_ | networkID is the ID of the network to which the subnet belongs. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the project owner of the subnet. | | MaxLength: 1024
Optional: \{\}
| +| `ipv6AddressMode` _string_ | ipv6AddressMode specifies mechanisms for assigning IPv6 IP addresses. | | MaxLength: 1024
Optional: \{\}
| +| `ipv6RAMode` _string_ | ipv6RAMode is the IPv6 router advertisement mode. It specifies
whether the networking service should transmit ICMPv6 packets. | | MaxLength: 1024
Optional: \{\}
| +| `subnetPoolID` _string_ | subnetPoolID is the id of the subnet pool associated with the subnet. | | MaxLength: 1024
Optional: \{\}
| +| `tags` _string array_ | tags optionally set via extensions/attributestags | | 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: \{\}
| #### SubnetSpec @@ -4127,11 +4127,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[SubnetImport](#subnetimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[SubnetResourceSpec](#subnetresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[SubnetImport](#subnetimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[SubnetResourceSpec](#subnetresourcespec)_ | 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: \{\}
| #### SubnetStatus @@ -4147,9 +4147,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[SubnetResourceStatus](#subnetresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[SubnetResourceStatus](#subnetresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Trunk @@ -4166,9 +4166,9 @@ Trunk is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Trunk` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[TrunkSpec](#trunkspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[TrunkStatus](#trunkstatus)_ | status defines the observed state of the resource. | | | +| `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` _[TrunkSpec](#trunkspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[TrunkStatus](#trunkstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### TrunkFilter @@ -4185,15 +4185,15 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. | | | -| `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
| -| `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
| -| `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
| -| `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
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _[NeutronDescription](#neutrondescription)_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
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: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. | | 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: \{\}
| #### TrunkImport @@ -4212,8 +4212,8 @@ _Appears in:_ | 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
| -| `filter` _[TrunkFilter](#trunkfilter)_ | 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
| +| `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` _[TrunkFilter](#trunkfilter)_ | 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: \{\}
| #### TrunkResourceSpec @@ -4229,13 +4229,13 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. If false (down),
the trunk does not forward packets. | | | -| `subports` _[TrunkSubportSpec](#trunksubportspec) array_ | subports is the list of ports to attach to the trunk. | | MaxItems: 1024
| -| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of Neutron tags to apply to the trunk. | | MaxItems: 64
MaxLength: 255
MinLength: 1
| +| `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` _[NeutronDescription](#neutrondescription)_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. If false (down),
the trunk does not forward packets. | | Optional: \{\}
| +| `subports` _[TrunkSubportSpec](#trunksubportspec) array_ | subports is the list of ports to attach to the trunk. | | MaxItems: 1024
Optional: \{\}
| +| `tags` _[NeutronTag](#neutrontag) array_ | tags is a list of Neutron tags to apply to the trunk. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Optional: \{\}
| #### TrunkResourceStatus @@ -4251,18 +4251,18 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `portID` _string_ | portID is the ID of the Port to which the resource is associated. | | MaxLength: 1024
| -| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
| -| `tenantID` _string_ | tenantID is the project owner of the trunk (alias of projectID in some deployments). | | MaxLength: 1024
| -| `status` _string_ | status indicates whether the trunk is currently operational. | | MaxLength: 1024
| -| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 64
items:MaxLength: 1024
| -| `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 | | | -| `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 | | | -| `revisionNumber` _integer_ | revisionNumber optionally set via extensions/standard-attr-revisions | | | -| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. | | | -| `subports` _[TrunkSubportStatus](#trunksubportstatus) array_ | subports is a list of ports associated with the trunk. | | MaxItems: 1024
| +| `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: \{\}
| +| `portID` _string_ | portID is the ID of the Port to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the ID of the Project to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `tenantID` _string_ | tenantID is the project owner of the trunk (alias of projectID in some deployments). | | MaxLength: 1024
Optional: \{\}
| +| `status` _string_ | status indicates whether the trunk is currently operational. | | MaxLength: 1024
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: \{\}
| +| `adminStateUp` _boolean_ | adminStateUp is the administrative state of the trunk. | | Optional: \{\}
| +| `subports` _[TrunkSubportStatus](#trunksubportstatus) array_ | subports is a list of ports associated with the trunk. | | MaxItems: 1024
Optional: \{\}
| #### TrunkSpec @@ -4278,11 +4278,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[TrunkImport](#trunkimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[TrunkResourceSpec](#trunkresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[TrunkImport](#trunkimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[TrunkResourceSpec](#trunkresourcespec)_ | 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: \{\}
| #### TrunkStatus @@ -4298,9 +4298,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[TrunkResourceStatus](#trunkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[TrunkResourceStatus](#trunkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### TrunkSubportSpec @@ -4317,9 +4317,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port that will be attached as a subport. | | MaxLength: 253
MinLength: 1
| -| `segmentationID` _integer_ | segmentationID is the segmentation ID for the subport (e.g. VLAN ID). | | Maximum: 4094
Minimum: 1
| -| `segmentationType` _string_ | segmentationType is the segmentation type for the subport (e.g. vlan). | | Enum: [inherit vlan]
MaxLength: 32
MinLength: 1
| +| `portRef` _[KubernetesNameRef](#kubernetesnameref)_ | portRef is a reference to the ORC Port that will be attached as a subport. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `segmentationID` _integer_ | segmentationID is the segmentation ID for the subport (e.g. VLAN ID). | | Maximum: 4094
Minimum: 1
Required: \{\}
| +| `segmentationType` _string_ | segmentationType is the segmentation type for the subport (e.g. vlan). | | Enum: [inherit vlan]
MaxLength: 32
MinLength: 1
Required: \{\}
| #### TrunkSubportStatus @@ -4336,9 +4336,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `portID` _string_ | portID is the OpenStack ID of the Port attached as a subport. | | MaxLength: 1024
| -| `segmentationID` _integer_ | segmentationID is the segmentation ID for the subport (e.g. VLAN ID). | | | -| `segmentationType` _string_ | segmentationType is the segmentation type for the subport (e.g. vlan). | | MaxLength: 1024
| +| `portID` _string_ | portID is the OpenStack ID of the Port attached as a subport. | | MaxLength: 1024
Optional: \{\}
| +| `segmentationID` _integer_ | segmentationID is the segmentation ID for the subport (e.g. VLAN ID). | | Optional: \{\}
| +| `segmentationType` _string_ | segmentationType is the segmentation type for the subport (e.g. vlan). | | MaxLength: 1024
Optional: \{\}
| @@ -4357,9 +4357,9 @@ User is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `User` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[UserSpec](#userspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[UserStatus](#userstatus)_ | status defines the observed state of the resource. | | | +| `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` _[UserSpec](#userspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[UserStatus](#userstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### UserDataSpec @@ -4377,7 +4377,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the user data for this server. | | MaxLength: 253
MinLength: 1
| +| `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the user data for this server. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### UserFilter @@ -4394,8 +4394,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### UserImport @@ -4414,8 +4414,8 @@ _Appears in:_ | 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
| -| `filter` _[UserFilter](#userfilter)_ | 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
| +| `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` _[UserFilter](#userfilter)_ | 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: \{\}
| #### UserResourceSpec @@ -4431,11 +4431,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | | +| `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: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
| #### UserResourceStatus @@ -4451,11 +4451,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
| -| `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
| -| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | | +| `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: \{\}
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| +| `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
Optional: \{\}
| +| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
| #### UserSpec @@ -4471,11 +4471,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[UserImport](#userimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[UserResourceSpec](#userresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[UserImport](#userimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[UserResourceSpec](#userresourcespec)_ | 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: \{\}
| #### UserStatus @@ -4491,9 +4491,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[UserResourceStatus](#userresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[UserResourceStatus](#userresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### Volume @@ -4510,9 +4510,9 @@ Volume is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `Volume` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[VolumeSpec](#volumespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[VolumeStatus](#volumestatus)_ | status defines the observed state of the resource. | | | +| `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` _[VolumeSpec](#volumespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[VolumeStatus](#volumestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### VolumeAttachmentStatus @@ -4528,10 +4528,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `attachmentID` _string_ | attachmentID represents the attachment UUID. | | MaxLength: 1024
| -| `serverID` _string_ | serverID is the UUID of the server to which the volume is attached. | | MaxLength: 1024
| -| `device` _string_ | device is the name of the device in the instance. | | MaxLength: 1024
| -| `attachedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | attachedAt shows the date and time when the resource was attached. The date and time stamp format is ISO 8601. | | | +| `attachmentID` _string_ | attachmentID represents the attachment UUID. | | MaxLength: 1024
Optional: \{\}
| +| `serverID` _string_ | serverID is the UUID of the server to which the volume is attached. | | MaxLength: 1024
Optional: \{\}
| +| `device` _string_ | device is the name of the device in the instance. | | MaxLength: 1024
Optional: \{\}
| +| `attachedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | attachedAt shows the date and time when the resource was attached. The date and time stamp format is ISO 8601. | | Optional: \{\}
| #### VolumeFilter @@ -4548,10 +4548,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `size` _integer_ | size is the size of the volume in GiB. | | Minimum: 1
| -| `availabilityZone` _string_ | availabilityZone is the availability zone of the existing resource | | MaxLength: 255
| +| `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: \{\}
| +| `size` _integer_ | size is the size of the volume in GiB. | | Minimum: 1
Optional: \{\}
| +| `availabilityZone` _string_ | availabilityZone is the availability zone of the existing resource | | MaxLength: 255
Optional: \{\}
| #### VolumeImport @@ -4570,8 +4570,8 @@ _Appears in:_ | 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
| -| `filter` _[VolumeFilter](#volumefilter)_ | 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
| +| `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` _[VolumeFilter](#volumefilter)_ | 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: \{\}
| #### VolumeMetadata @@ -4587,8 +4587,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the name of the metadata | | MaxLength: 255
| -| `value` _string_ | value is the value of the metadata | | MaxLength: 255
| +| `name` _string_ | name is the name of the metadata | | MaxLength: 255
Required: \{\}
| +| `value` _string_ | value is the value of the metadata | | MaxLength: 255
Required: \{\}
| #### VolumeMetadataStatus @@ -4604,8 +4604,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the name of the metadata | | MaxLength: 255
| -| `value` _string_ | value is the value of the metadata | | MaxLength: 255
| +| `name` _string_ | name is the name of the metadata | | MaxLength: 255
Optional: \{\}
| +| `value` _string_ | value is the value of the metadata | | MaxLength: 255
Optional: \{\}
| #### VolumeResourceSpec @@ -4621,13 +4621,13 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `size` _integer_ | size is the size of the volume, in gibibytes (GiB). | | Minimum: 1
| -| `volumeTypeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeTypeRef is a reference to the ORC VolumeType which this resource is associated with. | | MaxLength: 253
MinLength: 1
| -| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the volume. | | MaxLength: 255
| -| `metadata` _[VolumeMetadata](#volumemetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
| -| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef is a reference to an ORC Image. If specified, creates a
bootable volume from this image. The volume size must be >= the
image's min_disk requirement. | | MaxLength: 253
MinLength: 1
| +| `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: \{\}
| +| `size` _integer_ | size is the size of the volume, in gibibytes (GiB). | | Minimum: 1
Required: \{\}
| +| `volumeTypeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeTypeRef is a reference to the ORC VolumeType which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the volume. | | MaxLength: 255
Optional: \{\}
| +| `metadata` _[VolumeMetadata](#volumemetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
Optional: \{\}
| +| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef is a reference to an ORC Image. If specified, creates a
bootable volume from this image. The volume size must be >= the
image's min_disk requirement. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### VolumeResourceStatus @@ -4643,28 +4643,28 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `size` _integer_ | size is the size of the volume in GiB. | | | -| `status` _string_ | status represents the current status of the volume. | | MaxLength: 1024
| -| `availabilityZone` _string_ | availabilityZone is which availability zone the volume is in. | | MaxLength: 1024
| -| `attachments` _[VolumeAttachmentStatus](#volumeattachmentstatus) array_ | attachments is a list of attachments for the volume. | | MaxItems: 32
| -| `volumeType` _string_ | volumeType is the name of associated the volume type. | | MaxLength: 1024
| -| `snapshotID` _string_ | snapshotID is the ID of the snapshot from which the volume was created | | MaxLength: 1024
| -| `sourceVolID` _string_ | sourceVolID is the ID of another block storage volume from which the current volume was created | | MaxLength: 1024
| -| `backupID` _string_ | backupID is the ID of the backup from which the volume was restored | | MaxLength: 1024
| -| `metadata` _[VolumeMetadataStatus](#volumemetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
| -| `userID` _string_ | userID is the ID of the user who created the volume. | | MaxLength: 1024
| -| `bootable` _boolean_ | bootable indicates whether this is a bootable volume. | | | -| `imageID` _string_ | imageID is the ID of the image this volume was created from, if any. | | MaxLength: 1024
| -| `encrypted` _boolean_ | encrypted denotes if the volume is encrypted. | | | -| `replicationStatus` _string_ | replicationStatus is the status of replication. | | MaxLength: 1024
| -| `consistencyGroupID` _string_ | consistencyGroupID is the consistency group ID. | | MaxLength: 1024
| -| `multiattach` _boolean_ | multiattach denotes if the volume is multi-attach capable. | | | -| `host` _string_ | host is the identifier of the host holding the volume. | | MaxLength: 1024
| -| `tenantID` _string_ | tenantID is the ID of the project that owns the volume. | | MaxLength: 1024
| -| `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 | | | -| `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 | | | +| `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: \{\}
| +| `size` _integer_ | size is the size of the volume in GiB. | | Optional: \{\}
| +| `status` _string_ | status represents the current status of the volume. | | MaxLength: 1024
Optional: \{\}
| +| `availabilityZone` _string_ | availabilityZone is which availability zone the volume is in. | | MaxLength: 1024
Optional: \{\}
| +| `attachments` _[VolumeAttachmentStatus](#volumeattachmentstatus) array_ | attachments is a list of attachments for the volume. | | MaxItems: 32
Optional: \{\}
| +| `volumeType` _string_ | volumeType is the name of associated the volume type. | | MaxLength: 1024
Optional: \{\}
| +| `snapshotID` _string_ | snapshotID is the ID of the snapshot from which the volume was created | | MaxLength: 1024
Optional: \{\}
| +| `sourceVolID` _string_ | sourceVolID is the ID of another block storage volume from which the current volume was created | | MaxLength: 1024
Optional: \{\}
| +| `backupID` _string_ | backupID is the ID of the backup from which the volume was restored | | MaxLength: 1024
Optional: \{\}
| +| `metadata` _[VolumeMetadataStatus](#volumemetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
Optional: \{\}
| +| `userID` _string_ | userID is the ID of the user who created the volume. | | MaxLength: 1024
Optional: \{\}
| +| `bootable` _boolean_ | bootable indicates whether this is a bootable volume. | | Optional: \{\}
| +| `imageID` _string_ | imageID is the ID of the image this volume was created from, if any. | | MaxLength: 1024
Optional: \{\}
| +| `encrypted` _boolean_ | encrypted denotes if the volume is encrypted. | | Optional: \{\}
| +| `replicationStatus` _string_ | replicationStatus is the status of replication. | | MaxLength: 1024
Optional: \{\}
| +| `consistencyGroupID` _string_ | consistencyGroupID is the consistency group ID. | | MaxLength: 1024
Optional: \{\}
| +| `multiattach` _boolean_ | multiattach denotes if the volume is multi-attach capable. | | Optional: \{\}
| +| `host` _string_ | host is the identifier of the host holding the volume. | | MaxLength: 1024
Optional: \{\}
| +| `tenantID` _string_ | tenantID is the ID of the project that owns the volume. | | 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: \{\}
| #### VolumeSpec @@ -4680,11 +4680,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[VolumeImport](#volumeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[VolumeResourceSpec](#volumeresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[VolumeImport](#volumeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[VolumeResourceSpec](#volumeresourcespec)_ | 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: \{\}
| #### VolumeStatus @@ -4700,9 +4700,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[VolumeResourceStatus](#volumeresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[VolumeResourceStatus](#volumeresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### VolumeType @@ -4719,9 +4719,9 @@ VolumeType is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `VolumeType` | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[VolumeTypeSpec](#volumetypespec)_ | spec specifies the desired state of the resource. | | | -| `status` _[VolumeTypeStatus](#volumetypestatus)_ | status defines the observed state of the resource. | | | +| `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` _[VolumeTypeSpec](#volumetypespec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[VolumeTypeStatus](#volumetypestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| #### VolumeTypeExtraSpec @@ -4737,8 +4737,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the name of the extraspec | | MaxLength: 255
| -| `value` _string_ | value is the value of the extraspec | | MaxLength: 255
| +| `name` _string_ | name is the name of the extraspec | | MaxLength: 255
Required: \{\}
| +| `value` _string_ | value is the value of the extraspec | | MaxLength: 255
Required: \{\}
| #### VolumeTypeExtraSpecStatus @@ -4754,8 +4754,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is the name of the extraspec | | MaxLength: 255
| -| `value` _string_ | value is the value of the extraspec | | MaxLength: 255
| +| `name` _string_ | name is the name of the extraspec | | MaxLength: 255
Optional: \{\}
| +| `value` _string_ | value is the value of the extraspec | | MaxLength: 255
Optional: \{\}
| #### VolumeTypeFilter @@ -4772,9 +4772,9 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
| -| `isPublic` _boolean_ | isPublic indicates whether the VolumeType is public. | | | +| `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: \{\}
| +| `isPublic` _boolean_ | isPublic indicates whether the VolumeType is public. | | Optional: \{\}
| #### VolumeTypeImport @@ -4793,8 +4793,8 @@ _Appears in:_ | 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
| -| `filter` _[VolumeTypeFilter](#volumetypefilter)_ | 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
| +| `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` _[VolumeTypeFilter](#volumetypefilter)_ | 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: \{\}
| #### VolumeTypeResourceSpec @@ -4810,10 +4810,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `extraSpecs` _[VolumeTypeExtraSpec](#volumetypeextraspec) array_ | extraSpecs is a map of key-value pairs that define extra specifications for the volume type. | | MaxItems: 64
| -| `isPublic` _boolean_ | isPublic indicates whether the volume type is public. | | | +| `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: \{\}
| +| `extraSpecs` _[VolumeTypeExtraSpec](#volumetypeextraspec) array_ | extraSpecs is a map of key-value pairs that define extra specifications for the volume type. | | MaxItems: 64
Optional: \{\}
| +| `isPublic` _boolean_ | isPublic indicates whether the volume type is public. | | Optional: \{\}
| #### VolumeTypeResourceStatus @@ -4829,10 +4829,10 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `name` _string_ | name is a Human-readable name for the resource. Might not be unique. | | MaxLength: 1024
| -| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
| -| `extraSpecs` _[VolumeTypeExtraSpecStatus](#volumetypeextraspecstatus) array_ | extraSpecs is a map of key-value pairs that define extra specifications for the volume type. | | MaxItems: 64
| -| `isPublic` _boolean_ | isPublic indicates whether the VolumeType is public. | | | +| `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: \{\}
| +| `extraSpecs` _[VolumeTypeExtraSpecStatus](#volumetypeextraspecstatus) array_ | extraSpecs is a map of key-value pairs that define extra specifications for the volume type. | | MaxItems: 64
Optional: \{\}
| +| `isPublic` _boolean_ | isPublic indicates whether the VolumeType is public. | | Optional: \{\}
| #### VolumeTypeSpec @@ -4848,11 +4848,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[VolumeTypeImport](#volumetypeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[VolumeTypeResourceSpec](#volumetyperesourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | | -| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
| -| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | | -| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | | +| `import` _[VolumeTypeImport](#volumetypeimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[VolumeTypeResourceSpec](#volumetyperesourcespec)_ | 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: \{\}
| #### VolumeTypeStatus @@ -4868,8 +4868,8 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
| -| `resource` _[VolumeTypeResourceStatus](#volumetyperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +| `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` _[VolumeTypeResourceStatus](#volumetyperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| From bf855a18a410e802b7ef9b33f105bd6b77dd9802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 23 Mar 2026 17:47:02 +0100 Subject: [PATCH 101/121] Do not set go patch version This came from a mistake in gophercloud that was fixed with v2.11.1. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 977da4ba3..50b26e958 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/k-orc/openstack-resource-controller/v2 -go 1.25.7 +go 1.25.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc From 3ec55b4b67a02a26a9103b627b4ab2d394fcf7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 24 Mar 2026 09:27:18 +0100 Subject: [PATCH 102/121] Bump golang.org/x/net Fixes https://pkg.go.dev/vuln/GO-2026-4559. --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index cd0606913..ccb6f05e4 100644 --- a/go.mod +++ b/go.mod @@ -85,11 +85,11 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.50.0 // indirect + golang.org/x/net v0.52.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.40.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.41.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.42.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect diff --git a/go.sum b/go.sum index e15e232b0..34971a3f3 100644 --- a/go.sum +++ b/go.sum @@ -213,8 +213,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -225,10 +225,10 @@ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= From 72be082eeacc4adf652f65f0691244f324077dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 24 Mar 2026 12:53:54 +0100 Subject: [PATCH 103/121] ci: fix unsound condition in label-issue workflow The if: condition used two separate ${{ }} expansions with == outside the expression context, causing the condition to always evaluate to true. Move the comparison inside a single expression so it is properly evaluated by the GitHub Actions expression engine. Reported by zizmor (unsound-condition). --- .github/workflows/label-issue.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-issue.yaml b/.github/workflows/label-issue.yaml index 45ab4cbe3..831c6a8a4 100644 --- a/.github/workflows/label-issue.yaml +++ b/.github/workflows/label-issue.yaml @@ -7,7 +7,7 @@ on: jobs: clear_needinfo: name: Clear needinfo - if: ${{ github.event.issue.user.login }} == ${{ github.event.comment.user.login }} + if: github.event.issue.user.login == github.event.comment.user.login runs-on: ubuntu-latest permissions: issues: write From d9d13ee93317e503f37cf9453273fb6fa914afc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 24 Mar 2026 12:54:12 +0100 Subject: [PATCH 104/121] ci: fix template injection in label-pr workflow Pass github.base_ref through an environment variable instead of interpolating it directly into the run block. Direct interpolation of attacker-controllable context values into shell commands allows code injection. Reported by zizmor (template-injection). --- .github/workflows/label-pr.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 55913c024..15c33b9f6 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -23,9 +23,10 @@ jobs: run: | git config --global user.email "localrebase@k-orc.cloud" git config --global user.name "Local rebase" - git rebase -i origin/${{ github.base_ref }} + git rebase -i origin/$BASE_REF env: GIT_SEQUENCE_EDITOR: '/usr/bin/true' + BASE_REF: ${{ github.base_ref }} - name: Calculate go version id: vars From 267ac7e7b765f4a250d1e29aa7937891bed7c1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 24 Mar 2026 12:55:03 +0100 Subject: [PATCH 105/121] ci: set persist-credentials: false on all checkout actions Prevent the checked-out git repository from retaining the GITHUB_TOKEN in the git credential store. This reduces the risk of credential leakage through artifacts or subsequent steps. Reported by zizmor (artipacked). --- .github/workflows/container_image.yaml | 1 + .github/workflows/e2e.yaml | 2 ++ .github/workflows/ensure-labels.yaml | 2 ++ .github/workflows/generate.yaml | 2 ++ .github/workflows/go-lint.yaml | 2 ++ .github/workflows/label-pr.yaml | 1 + .github/workflows/pr-dependabot.yaml | 2 ++ .github/workflows/release_image.yaml | 1 + .github/workflows/unit.yml | 2 ++ .github/workflows/website.yaml | 2 ++ .github/workflows/weekly-security-scan.yaml | 1 + 11 files changed, 18 insertions(+) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index a8961e2e1..abd9adc1a 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -23,6 +23,7 @@ jobs: # build variables fetch-depth: 0 fetch-tags: true + persist-credentials: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 069e45339..31e938198 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -30,6 +30,8 @@ jobs: steps: - uses: actions/checkout@v6.0.2 + with: + persist-credentials: false - name: Deploy devstack uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index 21a654236..9b9504814 100644 --- a/.github/workflows/ensure-labels.yaml +++ b/.github/workflows/ensure-labels.yaml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 + with: + persist-credentials: false - uses: micnncim/action-label-syncer@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index 9bb3e7515..b249b46ac 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -14,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v6.0.2 + with: + persist-credentials: false - run: | make generate diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index bf34bb56d..f4d31ea7b 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -14,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v6.0.2 + with: + persist-credentials: false - name: Calculate go version id: vars diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 15c33b9f6..ec2e6fa0a 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -18,6 +18,7 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Rebase the PR against origin/github.base_ref to ensure actual API compatibility run: | diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index 92e80495c..c59d8bb53 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -20,6 +20,8 @@ jobs: steps: - name: Check out code into the Go module directory uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # tag=v4.2.2 + with: + persist-credentials: true # zizmor: ignore[artipacked] EndBug/add-and-commit needs git credentials to push - name: Calculate go version id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index 2924d8e73..befec5431 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -23,6 +23,7 @@ jobs: # build variables fetch-depth: 0 fetch-tags: true + persist-credentials: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Install build dependencies diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 93cb71c31..90e20e5a5 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v6.0.2 + with: + persist-credentials: false - name: Calculate go version id: vars diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 0ca6fd644..9772e2d46 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -18,6 +18,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6.0.2 + with: + persist-credentials: false - name: Pip install run: pip install -Ur website/requirements.txt diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index f833eb73c..261361fff 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -21,6 +21,7 @@ jobs: uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # tag=v4.2.2 with: ref: ${{ matrix.branch }} + persist-credentials: false - name: Calculate go version id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT From e712197f70bb060d9c77010394e200aa1943d61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 24 Mar 2026 12:56:12 +0100 Subject: [PATCH 106/121] ci: pin all GitHub Actions to commit SHAs Pin all action references to their full commit hashes instead of mutable tags. This prevents supply-chain attacks where a compromised tag could point to malicious code. The original tag is preserved in a trailing comment for maintainability. Reported by zizmor (unpinned-uses). --- .github/workflows/backport.yaml | 4 ++-- .github/workflows/container_image.yaml | 4 ++-- .github/workflows/e2e.yaml | 8 ++++---- .github/workflows/ensure-labels.yaml | 4 ++-- .github/workflows/generate.yaml | 2 +- .github/workflows/go-lint.yaml | 2 +- .github/workflows/label-pr.yaml | 6 +++--- .github/workflows/release_image.yaml | 4 ++-- .github/workflows/unit.yml | 2 +- .github/workflows/website.yaml | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 2e5388774..5a3af6a66 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -28,7 +28,7 @@ jobs: steps: - name: Generate a token from the orc-backport-bot github-app id: generate_token - uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 + uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # tag=v4.0.0 with: app_id: ${{ secrets.BACKPORT_APP_ID }} private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} @@ -37,7 +37,7 @@ jobs: if: > contains(github.event.pull_request.labels.*.name, 'semver:patch') || contains(github.event.label.name, 'semver:patch') - uses: kiegroup/git-backporting@82e45d73f8d39bc3d7eb4b41859d313696c93ed9 + uses: kiegroup/git-backporting@82e45d73f8d39bc3d7eb4b41859d313696c93ed9 # tag=v4.9.0 with: target-branch: release-1.0 pull-request: ${{ github.event.pull_request.url }} diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index abd9adc1a..eab870967 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: # Required for git describe to generate correct output for populating # build variables @@ -25,7 +25,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4 - run: | docker login -u="${{ secrets.QUAY_USERNAME }}" -p="${{ secrets.QUAY_TOKEN }}" quay.io diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 31e938198..349c89e60 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -29,19 +29,19 @@ jobs: runs-on: ubuntu-${{ matrix.ubuntu_version }} steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false - name: Deploy devstack - uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 + uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 # tag=v0.19 with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} enabled_services: "openstack-cli-server,neutron-trunk" - name: Deploy a Kind Cluster - uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # tag=v1.14.0 with: cluster_name: orc @@ -68,7 +68,7 @@ jobs: - name: Upload logs artifacts on failure if: failure() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7 with: name: e2e-${{ matrix.name }}-${{ github.run_id }} path: /tmp/artifacts/* diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index 9b9504814..bb1f461ed 100644 --- a/.github/workflows/ensure-labels.yaml +++ b/.github/workflows/ensure-labels.yaml @@ -13,10 +13,10 @@ jobs: ensure: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false - - uses: micnncim/action-label-syncer@v1 + - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # tag=v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index b249b46ac..c5b20503b 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index f4d31ea7b..5a520c6b8 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index ec2e6fa0a..29f9d533d 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -13,7 +13,7 @@ jobs: semver: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -43,7 +43,7 @@ jobs: # if semver=major, this will return RC=1, so let's ignore the failure so label # can be set later. We check for actual errors in the next step. continue-on-error: true - uses: joelanford/go-apidiff@60c4206be8f84348ebda2a3e0c3ac9cb54b8f685 + uses: joelanford/go-apidiff@60c4206be8f84348ebda2a3e0c3ac9cb54b8f685 # tag=v0.8.3 # go-apidiff returns RC=1 when semver=major, which makes the workflow to return # a failure. Instead let's just return a failure if go-apidiff failed to run. @@ -91,4 +91,4 @@ jobs: edits: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v6 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # tag=v6 diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index befec5431..f764a39db 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: # Required for git describe to generate correct output for populating # build variables @@ -25,7 +25,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4 - name: Install build dependencies run: sudo apt-get install -y libgpgme-dev diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 90e20e5a5..4dca40c4e 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -17,7 +17,7 @@ jobs: - '1' steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 9772e2d46..de460be2f 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -17,7 +17,7 @@ jobs: name: Publish to Cloudflare Pages steps: - name: Checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false @@ -28,7 +28,7 @@ jobs: run: mkdocs build --verbose --strict --config-file website/mkdocs.yml --site-dir rendered - name: Publish to Cloudflare Pages - uses: cloudflare/pages-action@v1 + uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # tag=v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} From f446d0ab102ab0e303b57a493cab4b51bdbfb46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 24 Mar 2026 12:56:44 +0100 Subject: [PATCH 107/121] ci: scope down workflow permissions to least privilege Add explicit empty permissions at the workflow level and grant only the required permissions at the job level. check-pr-labels needs no permissions beyond metadata. label-pr moves contents:read and pull-requests:write from workflow level to only the jobs that need them. Reported by zizmor (excessive-permissions). --- .github/workflows/check-pr-labels.yaml | 2 ++ .github/workflows/label-pr.yaml | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-pr-labels.yaml b/.github/workflows/check-pr-labels.yaml index 8d3cdde07..315323d41 100644 --- a/.github/workflows/check-pr-labels.yaml +++ b/.github/workflows/check-pr-labels.yaml @@ -9,6 +9,8 @@ on: - synchronize - unlabeled +permissions: {} + jobs: hold: if: github.event.pull_request.merged == false diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 29f9d533d..74d67c6f3 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -5,13 +5,14 @@ on: - opened - synchronize - reopened -permissions: - contents: read - pull-requests: write +permissions: {} jobs: semver: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: @@ -90,5 +91,8 @@ jobs: edits: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # tag=v6 From 933c4bb898324f04620b320a31b8e0b7e3e01902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 07:50:30 +0100 Subject: [PATCH 108/121] ci: split semver analysis from label-pr to fix pull_request_target security issue The semver job in label-pr.yaml checked out untrusted PR code and executed it (make go-version, go-apidiff) with access to a GITHUB_TOKEN that had pull-requests:write permissions. An attacker could modify the Makefile to exfiltrate the token. Split into two workflows following GitHub's recommended pattern: - semver.yaml: triggered by pull_request (read-only permissions), runs the analysis and uploads the result as an artifact. - label-pr.yaml: the semver-label job is triggered by workflow_run (when semver.yaml completes), downloads the artifact, and applies labels with write permissions. It never checks out untrusted code. The edits job stays in label-pr.yaml with pull_request_target as before (it only runs actions/labeler, no code checkout). Reported-by: Christopher Lusk --- .github/workflows/label-pr.yaml | 99 ++++++++++++++++----------------- .github/workflows/semver.yaml | 66 ++++++++++++++++++++++ 2 files changed, 113 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/semver.yaml diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 74d67c6f3..e5657ad3e 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -5,91 +5,86 @@ on: - opened - synchronize - reopened + workflow_run: + workflows: ["Semver analysis"] + types: + - completed + permissions: {} jobs: - semver: + semver-label: + if: github.event_name == 'workflow_run' runs-on: ubuntu-latest permissions: - contents: read + actions: read pull-requests: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - token: ${{ secrets.GITHUB_TOKEN }} - persist-credentials: false - - - name: Rebase the PR against origin/github.base_ref to ensure actual API compatibility + - name: Get PR number + id: pr run: | - git config --global user.email "localrebase@k-orc.cloud" - git config --global user.name "Local rebase" - git rebase -i origin/$BASE_REF + PR_NUMBER=$(gh api "repos/$REPO/commits/$HEAD_SHA/pulls" --jq '.[0].number') + if [ -z "$PR_NUMBER" ]; then + echo "Could not determine PR number" >&2 + exit 1 + fi + echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT env: - GIT_SEQUENCE_EDITOR: '/usr/bin/true' - BASE_REF: ${{ github.base_ref }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - - name: Calculate go version - id: vars - run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT + - name: Report failure + if: github.event.workflow_run.conclusion == 'failure' + run: | + gh pr edit "$NUMBER" --remove-label "semver:major,semver:minor,semver:patch" + gh issue comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ steps.pr.outputs.number }} + BODY: > + Failed to assess the semver bump. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}) for details. - - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + - name: Download semver results + if: github.event.workflow_run.conclusion == 'success' + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # tag=v4.3.0 with: - go-version: ${{ steps.vars.outputs.go_version }} - - - name: Checking Go API Compatibility - id: go-apidiff - # if semver=major, this will return RC=1, so let's ignore the failure so label - # can be set later. We check for actual errors in the next step. - continue-on-error: true - uses: joelanford/go-apidiff@60c4206be8f84348ebda2a3e0c3ac9cb54b8f685 # tag=v0.8.3 + name: semver-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} - # go-apidiff returns RC=1 when semver=major, which makes the workflow to return - # a failure. Instead let's just return a failure if go-apidiff failed to run. - - name: Return an error if Go API Compatibility couldn't be verified - if: steps.go-apidiff.outcome != 'success' && steps.go-apidiff.outputs.semver-type != 'major' - run: exit 1 + - name: Read semver type + if: github.event.workflow_run.conclusion == 'success' + id: semver + run: echo "type=$(cat semver-type)" >> $GITHUB_OUTPUT - name: Add label semver:patch - if: steps.go-apidiff.outputs.semver-type == 'patch' + if: github.event.workflow_run.conclusion == 'success' && steps.semver.outputs.type == 'patch' run: gh pr edit "$NUMBER" --add-label "semver:patch" --remove-label "semver:major,semver:minor" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} + NUMBER: ${{ steps.pr.outputs.number }} - name: Add label semver:minor - if: steps.go-apidiff.outputs.semver-type == 'minor' + if: github.event.workflow_run.conclusion == 'success' && steps.semver.outputs.type == 'minor' run: gh pr edit "$NUMBER" --add-label "semver:minor" --remove-label "semver:major,semver:patch" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} + NUMBER: ${{ steps.pr.outputs.number }} - name: Add label semver:major - if: steps.go-apidiff.outputs.semver-type == 'major' + if: github.event.workflow_run.conclusion == 'success' && steps.semver.outputs.type == 'major' run: gh pr edit "$NUMBER" --add-label "semver:major" --remove-label "semver:minor,semver:patch" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} - - - name: Report failure - if: failure() - run: | - gh pr edit "$NUMBER" --remove-label "semver:major,semver:minor,semver:patch" - gh issue comment "$NUMBER" --body "$BODY" - exit 1 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} - BODY: > - Failed to assess the semver bump. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + NUMBER: ${{ steps.pr.outputs.number }} edits: + if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml new file mode 100644 index 000000000..2509846c5 --- /dev/null +++ b/.github/workflows/semver.yaml @@ -0,0 +1,66 @@ +name: Semver analysis +on: + pull_request: + types: + - opened + - synchronize + - reopened + +permissions: + contents: read + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Rebase the PR against base ref to ensure actual API compatibility + run: | + git config --global user.email "localrebase@k-orc.cloud" + git config --global user.name "Local rebase" + git rebase -i origin/$BASE_REF + env: + GIT_SEQUENCE_EDITOR: '/usr/bin/true' + BASE_REF: ${{ github.base_ref }} + + - name: Calculate go version + id: vars + run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT + + - name: Set up Go + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + with: + go-version: ${{ steps.vars.outputs.go_version }} + + - name: Checking Go API Compatibility + id: go-apidiff + # if semver=major, this will return RC=1, so let's ignore the failure so label + # can be set later. We check for actual errors in the next step. + continue-on-error: true + uses: joelanford/go-apidiff@60c4206be8f84348ebda2a3e0c3ac9cb54b8f685 # tag=v0.8.3 + + # go-apidiff returns RC=1 when semver=major, which makes the workflow to return + # a failure. Instead let's just return a failure if go-apidiff failed to run. + - name: Return an error if Go API Compatibility couldn't be verified + if: steps.go-apidiff.outcome != 'success' && steps.go-apidiff.outputs.semver-type != 'major' + run: exit 1 + + - name: Save semver result + if: always() + run: | + mkdir -p semver-results + echo "$SEMVER_TYPE" > semver-results/semver-type + env: + SEMVER_TYPE: ${{ steps.go-apidiff.outputs.semver-type }} + + - name: Upload semver results + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7 + with: + name: semver-results + path: semver-results/ From 767d4c734025878e0d9974432014496c9ed7d007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 07:56:50 +0100 Subject: [PATCH 109/121] ci: replace pull_request_target with pull_request in check-pr-labels This workflow has no permissions, checks out no code, and accesses no secrets. It only reads the event payload to check for the hold label. pull_request is sufficient and avoids the security concerns of pull_request_target. --- .github/workflows/check-pr-labels.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-pr-labels.yaml b/.github/workflows/check-pr-labels.yaml index 315323d41..cf00b09e8 100644 --- a/.github/workflows/check-pr-labels.yaml +++ b/.github/workflows/check-pr-labels.yaml @@ -1,7 +1,7 @@ name: Ready on: merge_group: - pull_request_target: + pull_request: types: - labeled - opened From 060f823497664943771388349fef8060ad913606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 07:57:54 +0100 Subject: [PATCH 110/121] ci: add zizmor ignore comments for legitimate findings Suppress remaining zizmor findings that are intentional: - dangerous-triggers in backport.yaml: only runs on merged PRs, never checks out code. - dangerous-triggers in label-pr.yaml: pull_request_target only runs actions/labeler (no code checkout), workflow_run never executes untrusted code. - secrets-outside-env in backport.yaml, container_image.yaml, release_image.yaml, website.yaml: requires GitHub Environments which is an infrastructure change outside workflow files. --- .github/workflows/backport.yaml | 5 +++-- .github/workflows/container_image.yaml | 2 +- .github/workflows/label-pr.yaml | 2 ++ .github/workflows/release_image.yaml | 2 +- .github/workflows/website.yaml | 4 ++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 5a3af6a66..713212176 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -1,6 +1,7 @@ name: Pull Request backporting on: + # zizmor: ignore[dangerous-triggers] only runs on merged PRs, never checks out code pull_request_target: types: - closed @@ -30,8 +31,8 @@ jobs: id: generate_token uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # tag=v4.0.0 with: - app_id: ${{ secrets.BACKPORT_APP_ID }} - private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} + app_id: ${{ secrets.BACKPORT_APP_ID }} # zizmor: ignore[secrets-outside-env] + private_key: ${{ secrets.BACKPORT_APP_PRIVATE_KEY }} # zizmor: ignore[secrets-outside-env] - name: Backporting if: > diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index eab870967..a60a80586 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -28,7 +28,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4 - run: | - docker login -u="${{ secrets.QUAY_USERNAME }}" -p="${{ secrets.QUAY_TOKEN }}" quay.io + docker login -u="${{ secrets.QUAY_USERNAME }}" -p="${{ secrets.QUAY_TOKEN }}" quay.io # zizmor: ignore[secrets-outside-env] # Ensure we source identical build arguments for both builds source hack/version.sh && version::get_git_vars && version::get_build_date && \ make docker-buildx IMG=${{ env.image_tag_branch }} && \ diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index e5657ad3e..378ed3e0f 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -1,10 +1,12 @@ name: Label PR on: + # zizmor: ignore[dangerous-triggers] edits job only runs actions/labeler, no code checkout pull_request_target: types: - opened - synchronize - reopened + # zizmor: ignore[dangerous-triggers] semver-label job never checks out or executes untrusted code workflow_run: workflows: ["Semver analysis"] types: diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index f764a39db..8fbf2f332 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -30,7 +30,7 @@ jobs: run: sudo apt-get install -y libgpgme-dev - run: | - docker login -u="${{ secrets.QUAY_USERNAME }}" -p="${{ secrets.QUAY_TOKEN }}" quay.io + docker login -u="${{ secrets.QUAY_USERNAME }}" -p="${{ secrets.QUAY_TOKEN }}" quay.io # zizmor: ignore[secrets-outside-env] make docker-buildx IMG=${{ env.image_tag }} make build-bundle-image BUNDLE_IMG=${{ env.bundle_image_tag }} make docker-push IMG=${{ env.bundle_image_tag }} diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index de460be2f..1ee7dd42e 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -30,8 +30,8 @@ jobs: - name: Publish to Cloudflare Pages uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # tag=v1 with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} # zizmor: ignore[secrets-outside-env] + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} # zizmor: ignore[secrets-outside-env] projectName: k-orc directory: website/rendered gitHubToken: ${{ secrets.GITHUB_TOKEN }} From d2ee5deb0df8e9ca9eb5356b2e6fddcc5b69fd5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 10:13:58 +0100 Subject: [PATCH 111/121] ci: add zizmor workflow for GitHub Actions security scanning Run zizmor via the official zizmorcore/zizmor-action on pushes to main and pull requests that modify workflow files. Results are uploaded as SARIF to GitHub's Code Scanning / Security tab. --- .github/workflows/zizmor.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/zizmor.yaml diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml new file mode 100644 index 000000000..6a48b784f --- /dev/null +++ b/.github/workflows/zizmor.yaml @@ -0,0 +1,28 @@ +name: zizmor + +on: + push: + branches: + - main + paths: + - '.github/workflows/**' + pull_request: + paths: + - '.github/workflows/**' + +permissions: {} + +jobs: + zizmor: + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # tag=v0.5.2 From 015444b77eae8813f8c804b2925cff1d88059783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 17:21:37 +0100 Subject: [PATCH 112/121] docs: clarify API maturity and stability Address questions raised in #718 by updating the Maturity section to explicitly mention the v1alpha1 API status, note that core patterns are stable, and be transparent about the lack of a graduation timeline. --- README.md | 11 +++++++---- website/docs/index.md | 8 +++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c878505a3..6c343b281 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,13 @@ ORC is based on [Gophercloud][gophercloud], the OpenStack Go SDK. ## Maturity -While we currently cover a limited subset of OpenStack resources, we focus on -making existing controllers as correct and predictable as possible. We -encourage you to contribute, file issues, and help improve the project as we -continue to work on it! +ORC is deployed and used in production environments and is notably a dependency +of Cluster API's [OpenStack provider](https://github.com/kubernetes-sigs/cluster-api-provider-openstack). + +The Kubernetes API is currently `v1alpha1`. The core API patterns are stable and +we do not anticipate major structural changes, but the API is still evolving as +we add new controllers and features. We do not have a timeline for graduation to +`v1beta1`. ORC versioning follows [semver](https://semver.org/spec/v2.0.0.html): there will be no breaking changes within a major release. diff --git a/website/docs/index.md b/website/docs/index.md index 94bf015fb..aae4dc7cd 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -53,12 +53,14 @@ You define OpenStack resources as Kubernetes custom resources. ORC watches these ## Maturity -While we currently cover a limited subset of OpenStack resources, we focus on -making existing controllers as correct and predictable as possible. - ORC is deployed and used in production environments and is notably a dependency of Cluster API's [OpenStack provider][capo]. +The Kubernetes API is currently `v1alpha1`. The core API patterns are stable and +we do not anticipate major structural changes, but the API is still evolving as +we add new controllers and features. We do not have a timeline for graduation to +`v1beta1`. + ORC versioning follows [semver]: there will be no breaking changes within a major release. From ea0730e439c22bd39ee51451cdd46e05a59a981f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 17:59:44 +0100 Subject: [PATCH 113/121] ci: add dependabot cooldown configuration Add a 7-day cooldown to all Dependabot package ecosystem entries. This reduces supply-chain risk by waiting for newly released versions to be vetted before automatically proposing updates, and avoids pulling in compromised versions before they can be taken down. Reported by zizmor (dependabot-cooldown). --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 62321a86b..800e48911 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,8 @@ updates: schedule: interval: "weekly" day: "monday" + cooldown: + default-days: 7 target-branch: main groups: all-github-actions: @@ -23,6 +25,8 @@ updates: schedule: interval: "weekly" day: "monday" + cooldown: + default-days: 7 target-branch: main groups: all-go-mod-patch-and-minor: @@ -46,6 +50,8 @@ updates: schedule: interval: "weekly" day: "monday" + cooldown: + default-days: 7 target-branch: release-1.0 groups: all-github-actions: @@ -60,6 +66,8 @@ updates: schedule: interval: "weekly" day: "monday" + cooldown: + default-days: 7 target-branch: release-1.0 groups: all-go-mod-patch-and-minor: From 4057622765d18645a22569b204ea391502fe32f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 18:00:07 +0100 Subject: [PATCH 114/121] ci: update actions/checkout to v6.0.2 in pr-dependabot and weekly-security-scan These two workflows had a stale actions/checkout pin where the commit SHA did not match the version in the tag comment. Update both to v6.0.2 to match the rest of the repo. Reported by zizmor (ref-version-mismatch). --- .github/workflows/pr-dependabot.yaml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index c59d8bb53..4cd85de01 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # tag=v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: true # zizmor: ignore[artipacked] EndBug/add-and-commit needs git credentials to push - name: Calculate go version diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 261361fff..497198df9 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # tag=v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: ref: ${{ matrix.branch }} persist-credentials: false From c7b1682f4f47aadbb70aa7016259e6db951a97ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 25 Mar 2026 18:00:25 +0100 Subject: [PATCH 115/121] ci: expand zizmor scan scope to cover dependabot config Widen the path filter from .github/workflows/** to .github/** so that changes to .github/dependabot.yml also trigger the zizmor security scan. --- .github/workflows/zizmor.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index 6a48b784f..dc96f7d5e 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -5,10 +5,10 @@ on: branches: - main paths: - - '.github/workflows/**' + - '.github/**' pull_request: paths: - - '.github/workflows/**' + - '.github/**' permissions: {} From c2f56fb49bb9dcbe66c5cd860c7a70edbcf1a71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 26 Mar 2026 09:52:39 +0100 Subject: [PATCH 116/121] ci: pass PR number through artifact instead of API The commits/:sha/pulls API endpoint does not return results for fork PRs because the commit does not exist in the base repository. Instead, include the PR number in the artifact uploaded by the semver analysis workflow, where it is always available from the pull_request event context. This fixes up 933c4bb898324f04620b320a31b8e0b7e3e01902. --- .github/workflows/label-pr.yaml | 29 +++++++++-------------------- .github/workflows/semver.yaml | 2 ++ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 378ed3e0f..5ee015ff4 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -22,19 +22,16 @@ jobs: actions: read pull-requests: write steps: - - name: Get PR number + - name: Download semver results + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # tag=v4.3.0 + with: + name: semver-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Read PR number id: pr - run: | - PR_NUMBER=$(gh api "repos/$REPO/commits/$HEAD_SHA/pulls" --jq '.[0].number') - if [ -z "$PR_NUMBER" ]; then - echo "Could not determine PR number" >&2 - exit 1 - fi - echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: echo "number=$(cat pr-number)" >> $GITHUB_OUTPUT - name: Report failure if: github.event.workflow_run.conclusion == 'failure' @@ -48,14 +45,6 @@ jobs: BODY: > Failed to assess the semver bump. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}) for details. - - name: Download semver results - if: github.event.workflow_run.conclusion == 'success' - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # tag=v4.3.0 - with: - name: semver-results - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Read semver type if: github.event.workflow_run.conclusion == 'success' id: semver diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 2509846c5..6b846336e 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -55,8 +55,10 @@ jobs: run: | mkdir -p semver-results echo "$SEMVER_TYPE" > semver-results/semver-type + echo "$PR_NUMBER" > semver-results/pr-number env: SEMVER_TYPE: ${{ steps.go-apidiff.outputs.semver-type }} + PR_NUMBER: ${{ github.event.pull_request.number }} - name: Upload semver results if: always() From 6765552c04f51644a9e2dd202a3bec1664779cba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:42:42 +0000 Subject: [PATCH 117/121] :seedling:(deps): bump actions/download-artifact Bumps the all-github-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 4.3.0 to 8.0.1 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/d3f86a106a0bac45b974a628896c90dbdf5c8093...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/label-pr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 5ee015ff4..723dc1f87 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -23,7 +23,7 @@ jobs: pull-requests: write steps: - name: Download semver results - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # tag=v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # tag=v8.0.1 with: name: semver-results run-id: ${{ github.event.workflow_run.id }} From c77882f0c7d3f0cee3312077b325ba16c5592e7a Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Thu, 12 Mar 2026 15:22:11 +0000 Subject: [PATCH 118/121] user: add passwordRef for managed user creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `passwordRef` field to `UserResourceSpec` that references a Secret containing the user's password. Make this field required until we add the ability to expose auto-generated passwords from Keystone. This field is also currently immutable. Also populate `passwordExpiresAt` in status when Keystone has password expiration configured. Co-Authored-By: Martin André --- api/v1alpha1/user_types.go | 11 ++++++ cmd/models-schema/zz_generated.openapi.go | 15 ++++++++ .../bases/openstack.k-orc.cloud_users.yaml | 17 +++++++++ config/samples/openstack_v1alpha1_user.yaml | 37 ++++++++++++++++++- internal/controllers/user/actuator.go | 21 +++++++++++ internal/controllers/user/controller.go | 28 +++++++++++++- internal/controllers/user/status.go | 6 +++ .../tests/user-create-full/00-assert.yaml | 2 + .../user-create-full/00-create-resource.yaml | 11 +++++- .../tests/user-create-minimal/00-assert.yaml | 3 ++ .../00-create-resource.yaml | 11 +++++- .../user/tests/user-dependency/00-assert.yaml | 15 ++++++++ .../00-create-resources-missing-deps.yaml | 17 ++++++++- .../user/tests/user-dependency/00-secret.yaml | 10 ++++- .../user/tests/user-dependency/01-assert.yaml | 15 ++++++++ .../01-create-dependencies.yaml | 10 ++++- .../user-dependency/04-delete-resources.yaml | 5 ++- .../00-import-resource.yaml | 8 ++++ .../01-create-trap-resource.yaml | 3 +- .../02-create-resource.yaml | 3 +- .../00-create-resources.yaml | 13 ++++++- .../tests/user-import/00-import-resource.yaml | 8 ++++ .../user-import/01-create-trap-resource.yaml | 3 +- .../tests/user-import/02-create-resource.yaml | 3 +- .../user/tests/user-update/00-assert.yaml | 3 +- .../user-update/00-minimal-resource.yaml | 11 +++++- .../user-update/01-updated-resource.yaml | 2 +- .../user/tests/user-update/02-assert.yaml | 3 +- .../api/v1alpha1/userresourcespec.go | 9 +++++ .../api/v1alpha1/userresourcestatus.go | 19 +++++++--- .../applyconfiguration/internal/internal.go | 6 +++ test/apivalidations/user_test.go | 19 +++++++++- website/docs/crd-reference.md | 2 + 33 files changed, 325 insertions(+), 24 deletions(-) diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index faf317660..ddccb96e2 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -42,6 +42,12 @@ type UserResourceSpec struct { // enabled defines whether a user is enabled or disabled // +optional Enabled *bool `json:"enabled,omitempty"` + + // passwordRef is a reference to a Secret containing the password + // for this user. The Secret must contain a key named "password". + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="passwordRef is immutable" + PasswordRef KubernetesNameRef `json:"passwordRef,omitempty"` } // UserFilter defines an existing resource by its properties @@ -81,4 +87,9 @@ type UserResourceStatus struct { // enabled defines whether a user is enabled or disabled // +optional Enabled bool `json:"enabled,omitempty"` + + // passwordExpiresAt is the timestamp at which the user's password expires. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + PasswordExpiresAt string `json:"passwordExpiresAt,omitempty"` } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 9250f4e36..e13e9899b 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -11393,7 +11393,15 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref c Format: "", }, }, + "passwordRef": { + SchemaProps: spec.SchemaProps{ + Description: "passwordRef is a reference to a Secret containing the password for this user. The Secret must contain a key named \"password\".", + Type: []string{"string"}, + Format: "", + }, + }, }, + Required: []string{"passwordRef"}, }, }, } @@ -11441,6 +11449,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref Format: "", }, }, + "passwordExpiresAt": { + SchemaProps: spec.SchemaProps{ + Description: "passwordExpiresAt is the timestamp at which the user's password expires.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml index 2b7480257..201bb5eba 100644 --- a/config/crd/bases/openstack.k-orc.cloud_users.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -186,6 +186,18 @@ spec: minLength: 1 pattern: ^[^,]+$ type: string + passwordRef: + description: |- + passwordRef is a reference to a Secret containing the password + for this user. The Secret must contain a key named "password". + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: passwordRef is immutable + rule: self == oldSelf + required: + - passwordRef type: object required: - cloudCredentialsRef @@ -313,6 +325,11 @@ spec: not be unique. maxLength: 1024 type: string + passwordExpiresAt: + description: passwordExpiresAt is the timestamp at which the user's + password expires. + maxLength: 1024 + type: string type: object type: object required: diff --git a/config/samples/openstack_v1alpha1_user.yaml b/config/samples/openstack_v1alpha1_user.yaml index 09067e614..2e6371f2f 100644 --- a/config/samples/openstack_v1alpha1_user.yaml +++ b/config/samples/openstack_v1alpha1_user.yaml @@ -1,5 +1,35 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: user-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: user-sample +type: Opaque +stringData: + password: "TestPassword" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: name: user-sample @@ -9,4 +39,9 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - description: Sample User + name: user-sample + description: User sample + domainRef: user-sample + defaultProjectRef: user-sample + enabled: true + passwordRef: user-sample diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index 391205112..4b4683fe5 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -135,6 +135,26 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT defaultProjectID = ptr.Deref(project.Status.ID, "") } } + + var password string + { + secret, secretReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + &resource.PasswordRef, "Secret", + func(*corev1.Secret) bool { return true }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus) + if secretReconcileStatus == nil { + passwordBytes, ok := secret.Data["password"] + if !ok { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.NewReconcileStatus().WithProgressMessage("Password secret does not contain \"password\" key")) + } else { + password = string(passwordBytes) + } + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } @@ -144,6 +164,7 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT DomainID: domainID, Enabled: resource.Enabled, DefaultProjectID: defaultProjectID, + Password: password, } osResource, err := actuator.osClient.CreateUser(ctx, createOpts) diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go index 4e432c0c7..f69818726 100644 --- a/internal/controllers/user/controller.go +++ b/internal/controllers/user/controller.go @@ -20,6 +20,7 @@ import ( "context" "errors" + corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -86,6 +87,17 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.UserList, *or }, ) +var passwordDependency = dependency.NewDependency[*orcv1alpha1.UserList, *corev1.Secret]( + "spec.resource.passwordRef", + func(user *orcv1alpha1.User) []string { + resource := user.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.PasswordRef)} + }, +) + // SetupWithManager sets up the controller with the Manager. func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) @@ -106,8 +118,14 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr return err } + passwordWatchEventHandler, err := passwordDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). + For(&orcv1alpha1.User{}). Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), ). @@ -118,12 +136,20 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), ). - For(&orcv1alpha1.User{}) + // XXX: This is a general watch on secrets. A general watch on secrets + // is undesirable because: + // - It requires problematic RBAC + // - Secrets are arbitrarily large, and we don't want to cache their contents + // + // These will require separate solutions. For the latter we should + // probably use a MetadataOnly watch on secrets. + Watches(&corev1.Secret{}, passwordWatchEventHandler) if err := errors.Join( domainDependency.AddToManager(ctx, mgr), projectDependency.AddToManager(ctx, mgr), domainImportDependency.AddToManager(ctx, mgr), + passwordDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/user/status.go b/internal/controllers/user/status.go index 0d0f8da51..e412d66fd 100644 --- a/internal/controllers/user/status.go +++ b/internal/controllers/user/status.go @@ -17,6 +17,8 @@ limitations under the License. package user import ( + "time" + "github.com/go-logr/logr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -62,5 +64,9 @@ func (userStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou resourceStatus.WithDefaultProjectID(osResource.DefaultProjectID) } + if !osResource.PasswordExpiresAt.IsZero() { + resourceStatus.WithPasswordExpiresAt(osResource.PasswordExpiresAt.Format(time.RFC3339)) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/user/tests/user-create-full/00-assert.yaml b/internal/controllers/user/tests/user-create-full/00-assert.yaml index 0e9bd2a17..b91d5b2dd 100644 --- a/internal/controllers/user/tests/user-create-full/00-assert.yaml +++ b/internal/controllers/user/tests/user-create-full/00-assert.yaml @@ -35,3 +35,5 @@ assertAll: - celExpr: "user.status.id != ''" - celExpr: "user.status.resource.domainID == domain.status.id" - celExpr: "user.status.resource.defaultProjectID == project.status.id" + # passwordExpiresAt depends on the Keystone security_compliance + # configuration and is not asserted here. diff --git a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml index 4df449bda..53d6869bb 100644 --- a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml +++ b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml @@ -21,6 +21,14 @@ spec: managementPolicy: managed resource: {} --- +apiVersion: v1 +kind: Secret +metadata: + name: user-create-full +type: Opaque +stringData: + password: "TestPassword" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -35,4 +43,5 @@ spec: description: User from "create full" test domainRef: user-create-full defaultProjectRef: user-create-full - enabled: true \ No newline at end of file + enabled: true + passwordRef: user-create-full diff --git a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml index 950d429bd..f8ffcb148 100644 --- a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml +++ b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml @@ -27,3 +27,6 @@ assertAll: - celExpr: "!has(user.status.resource.description)" - celExpr: "user.status.resource.domainID == 'default'" - celExpr: "!has(user.status.resource.defaultProjectID)" + # passwordExpiresAt depends on the Keystone security_compliance + # configuration and is not asserted here. + diff --git a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml index c3d2147bf..72545e48c 100644 --- a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml +++ b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml @@ -1,4 +1,12 @@ --- +apiVersion: v1 +kind: Secret +metadata: + name: user-create-minimal +type: Opaque +stringData: + password: "TestPassword" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -8,4 +16,5 @@ spec: cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - resource: {} \ No newline at end of file + resource: + passwordRef: user-create-minimal \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/00-assert.yaml b/internal/controllers/user/tests/user-dependency/00-assert.yaml index 388f70d82..f45da298d 100644 --- a/internal/controllers/user/tests/user-dependency/00-assert.yaml +++ b/internal/controllers/user/tests/user-dependency/00-assert.yaml @@ -42,4 +42,19 @@ status: - type: Progressing message: Waiting for Project/user-dependency to be created status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-password +status: + conditions: + - type: Available + message: Waiting for Secret/user-dependency-password to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/user-dependency-password to be created + status: "True" reason: Progressing \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml index c5b59dafa..c06e90511 100644 --- a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml @@ -10,6 +10,7 @@ spec: managementPolicy: managed resource: domainRef: user-dependency + passwordRef: user-dependency-password-existing --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -22,6 +23,7 @@ spec: managementPolicy: managed resource: defaultProjectRef: user-dependency + passwordRef: user-dependency-password-existing --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -32,4 +34,17 @@ spec: cloudName: openstack-admin secretName: user-dependency managementPolicy: managed - resource: {} \ No newline at end of file + resource: + passwordRef: user-dependency-password-existing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-dependency-no-password +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + passwordRef: user-dependency-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/00-secret.yaml b/internal/controllers/user/tests/user-dependency/00-secret.yaml index 082860af5..1e9d5d5fb 100644 --- a/internal/controllers/user/tests/user-dependency/00-secret.yaml +++ b/internal/controllers/user/tests/user-dependency/00-secret.yaml @@ -3,4 +3,12 @@ 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 \ No newline at end of file + namespaced: true +--- +apiVersion: v1 +kind: Secret +metadata: + name: user-dependency-password-existing +type: Opaque +stringData: + password: "TestPassword" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-dependency/01-assert.yaml b/internal/controllers/user/tests/user-dependency/01-assert.yaml index 30bfee417..83de36848 100644 --- a/internal/controllers/user/tests/user-dependency/01-assert.yaml +++ b/internal/controllers/user/tests/user-dependency/01-assert.yaml @@ -33,6 +33,21 @@ apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: name: user-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: User +metadata: + name: user-dependency-no-password status: conditions: - type: Available diff --git a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml index 4a292db93..2823b9d99 100644 --- a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml +++ b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml @@ -25,4 +25,12 @@ spec: cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - resource: {} \ No newline at end of file + resource: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: user-dependency-password +type: Opaque +stringData: + password: "TestPassword" diff --git a/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml index 8054e0ebc..e0787a012 100644 --- a/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml +++ b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml @@ -10,4 +10,7 @@ delete: name: user-dependency-no-domain - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User - name: user-dependency-no-project \ No newline at end of file + name: user-dependency-no-project +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-dependency-no-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml index 0681c805b..6001315f4 100644 --- a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml @@ -1,4 +1,12 @@ --- +apiVersion: v1 +kind: Secret +metadata: + name: user-import-dependency-password +type: Opaque +stringData: + password: "TestPassword" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain metadata: diff --git a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml index 7154af7ba..48536462a 100644 --- a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml @@ -21,4 +21,5 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - domainRef: user-import-dependency-not-this-one \ No newline at end of file + domainRef: user-import-dependency-not-this-one + passwordRef: user-import-dependency-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml index ea64cab75..51c32bb06 100644 --- a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml @@ -20,4 +20,5 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - domainRef: user-import-dependency-external \ No newline at end of file + domainRef: user-import-dependency-external + passwordRef: user-import-dependency-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml index 6f4e6c034..10e809d1a 100644 --- a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml +++ b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml @@ -10,6 +10,14 @@ spec: managementPolicy: managed resource: {} --- +apiVersion: v1 +kind: Secret +metadata: + name: user-import-error-password +type: Opaque +stringData: + password: "TestPassword" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -22,7 +30,7 @@ spec: resource: description: User from "import error" test domainRef: user-import-error-domain - + passwordRef: user-import-error-password --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -35,4 +43,5 @@ spec: managementPolicy: managed resource: description: User from "import error" test - domainRef: user-import-error-domain \ No newline at end of file + domainRef: user-import-error-domain + passwordRef: user-import-error-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/00-import-resource.yaml b/internal/controllers/user/tests/user-import/00-import-resource.yaml index d8b7199fa..a80dc427a 100644 --- a/internal/controllers/user/tests/user-import/00-import-resource.yaml +++ b/internal/controllers/user/tests/user-import/00-import-resource.yaml @@ -1,4 +1,12 @@ --- +apiVersion: v1 +kind: Secret +metadata: + name: user-import-password +type: Opaque +stringData: + password: "TestPassword" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain metadata: diff --git a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml index ea393341f..18ae8d89a 100644 --- a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml +++ b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml @@ -13,4 +13,5 @@ spec: managementPolicy: managed resource: description: User user-import-external from "user-import" test - domainRef: user-import-external \ No newline at end of file + domainRef: user-import-external + passwordRef: user-import-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/02-create-resource.yaml b/internal/controllers/user/tests/user-import/02-create-resource.yaml index 43a43ef04..ca3cb03fc 100644 --- a/internal/controllers/user/tests/user-import/02-create-resource.yaml +++ b/internal/controllers/user/tests/user-import/02-create-resource.yaml @@ -10,4 +10,5 @@ spec: managementPolicy: managed resource: description: User user-import-external from "user-import" test - domainRef: user-import-external \ No newline at end of file + domainRef: user-import-external + passwordRef: user-import-password \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/00-assert.yaml b/internal/controllers/user/tests/user-update/00-assert.yaml index 1cd41ff5a..c7a2749fc 100644 --- a/internal/controllers/user/tests/user-update/00-assert.yaml +++ b/internal/controllers/user/tests/user-update/00-assert.yaml @@ -10,7 +10,8 @@ assertAll: - celExpr: "!has(user.status.resource.description)" - celExpr: "user.status.resource.domainID == 'default'" - celExpr: "!has(user.status.resource.defaultProjectID)" - - celExpr: "!has(user.status.resource.passwordExpiresAt)" + # passwordExpiresAt depends on the Keystone security_compliance + # configuration and is not asserted here. --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User diff --git a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml index 02960585c..d980e382a 100644 --- a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml +++ b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml @@ -1,4 +1,12 @@ --- +apiVersion: v1 +kind: Secret +metadata: + name: user-update +type: Opaque +stringData: + password: "TestPassword" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -8,4 +16,5 @@ spec: cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - resource: {} \ No newline at end of file + resource: + passwordRef: user-update \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update/01-updated-resource.yaml b/internal/controllers/user/tests/user-update/01-updated-resource.yaml index 4cbafe8c3..dea4f5476 100644 --- a/internal/controllers/user/tests/user-update/01-updated-resource.yaml +++ b/internal/controllers/user/tests/user-update/01-updated-resource.yaml @@ -7,4 +7,4 @@ spec: resource: name: user-update-updated description: user-update-updated - enabled: false \ No newline at end of file + enabled: false diff --git a/internal/controllers/user/tests/user-update/02-assert.yaml b/internal/controllers/user/tests/user-update/02-assert.yaml index 1c70b64e1..c2c14d837 100644 --- a/internal/controllers/user/tests/user-update/02-assert.yaml +++ b/internal/controllers/user/tests/user-update/02-assert.yaml @@ -8,7 +8,8 @@ resourceRefs: ref: user assertAll: - celExpr: "!has(user.status.resource.description)" - - celExpr: "!has(user.status.resource.passwordExpiresAt)" + # passwordExpiresAt depends on the Keystone security_compliance + # configuration and is not asserted here. --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go index ed4b86a2e..bd0bab7c6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go @@ -30,6 +30,7 @@ type UserResourceSpecApplyConfiguration struct { DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` DefaultProjectRef *apiv1alpha1.KubernetesNameRef `json:"defaultProjectRef,omitempty"` Enabled *bool `json:"enabled,omitempty"` + PasswordRef *apiv1alpha1.KubernetesNameRef `json:"passwordRef,omitempty"` } // UserResourceSpecApplyConfiguration constructs a declarative configuration of the UserResourceSpec type for use with @@ -77,3 +78,11 @@ func (b *UserResourceSpecApplyConfiguration) WithEnabled(value bool) *UserResour b.Enabled = &value return b } + +// WithPasswordRef sets the PasswordRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PasswordRef field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithPasswordRef(value apiv1alpha1.KubernetesNameRef) *UserResourceSpecApplyConfiguration { + b.PasswordRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go index 05093ff79..c23b0b6cf 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go @@ -21,11 +21,12 @@ package v1alpha1 // UserResourceStatusApplyConfiguration represents a declarative configuration of the UserResourceStatus type for use // with apply. type UserResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - DomainID *string `json:"domainID,omitempty"` - DefaultProjectID *string `json:"defaultProjectID,omitempty"` - Enabled *bool `json:"enabled,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DomainID *string `json:"domainID,omitempty"` + DefaultProjectID *string `json:"defaultProjectID,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + PasswordExpiresAt *string `json:"passwordExpiresAt,omitempty"` } // UserResourceStatusApplyConfiguration constructs a declarative configuration of the UserResourceStatus type for use with @@ -73,3 +74,11 @@ func (b *UserResourceStatusApplyConfiguration) WithEnabled(value bool) *UserReso b.Enabled = &value return b } + +// WithPasswordExpiresAt sets the PasswordExpiresAt field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PasswordExpiresAt field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithPasswordExpiresAt(value string) *UserResourceStatusApplyConfiguration { + b.PasswordExpiresAt = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 216436517..c641e66f4 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3409,6 +3409,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: name type: scalar: string + - name: passwordRef + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus map: fields: @@ -3427,6 +3430,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: name type: scalar: string + - name: passwordExpiresAt + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserSpec map: fields: diff --git a/test/apivalidations/user_test.go b/test/apivalidations/user_test.go index cc7024b9e..15057eb85 100644 --- a/test/apivalidations/user_test.go +++ b/test/apivalidations/user_test.go @@ -41,7 +41,8 @@ func userStub(namespace *corev1.Namespace) *orcv1alpha1.User { } func testUserResource() *applyconfigv1alpha1.UserResourceSpecApplyConfiguration { - return applyconfigv1alpha1.UserResourceSpec() + return applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("user-password") } func baseUserPatch(user client.Object) *applyconfigv1alpha1.UserApplyConfiguration { @@ -95,10 +96,12 @@ var _ = Describe("ORC User API validations", func() { user := userStub(namespace) patch := baseUserPatch(user) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("user-password"). WithDomainRef("domain-a")) Expect(applyObj(ctx, user, patch)).To(Succeed()) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("user-password"). WithDomainRef("domain-b")) Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) }) @@ -107,11 +110,25 @@ var _ = Describe("ORC User API validations", func() { user := userStub(namespace) patch := baseUserPatch(user) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("user-password"). WithDefaultProjectRef("project-a")) Expect(applyObj(ctx, user, patch)).To(Succeed()) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("user-password"). WithDefaultProjectRef("project-b")) Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("defaultProjectRef is immutable"))) }) + + It("should have immutable passwordRef", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("password-a")) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("password-b")) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("passwordRef is immutable"))) + }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 8d22de9fc..07dbfbe88 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -4436,6 +4436,7 @@ _Appears in:_ | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
| +| `passwordRef` _[KubernetesNameRef](#kubernetesnameref)_ | passwordRef is a reference to a Secret containing the password
for this user. The Secret must contain a key named "password". | | MaxLength: 253
MinLength: 1
Required: \{\}
| #### UserResourceStatus @@ -4456,6 +4457,7 @@ _Appears in:_ | `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
| | `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
Optional: \{\}
| | `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
| +| `passwordExpiresAt` _string_ | passwordExpiresAt is the timestamp at which the user's password expires. | | MaxLength: 1024
Optional: \{\}
| #### UserSpec From 28a09a40e006e2baf99d849e010914d192e25ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 30 Mar 2026 11:43:23 +0200 Subject: [PATCH 119/121] user: implement password mutability via passwordRef Make the passwordRef field mutable so users can update passwords by pointing to a different Secret. Track the applied password reference in a new status field (appliedPasswordRef) to detect when an update is needed. The reconciler compares spec.resource.passwordRef with status.resource.appliedPasswordRef. On first reconcile after creation, it sets the status field without calling UpdateUser (CreateResource already set the initial password). On subsequent changes, it reads the new Secret, calls UpdateUser, and updates the status field via a MergePatch that coexists with the main SSA status update. --- api/v1alpha1/user_types.go | 7 +- cmd/models-schema/zz_generated.openapi.go | 7 ++ .../bases/openstack.k-orc.cloud_users.yaml | 9 ++- internal/controllers/user/actuator.go | 73 +++++++++++++++++++ .../user/tests/user-update/00-assert.yaml | 1 + .../user/tests/user-update/01-assert.yaml | 1 + .../user-update/01-updated-resource.yaml | 9 +++ .../user/tests/user-update/02-assert.yaml | 1 + .../user/tests/user-update/README.md | 2 +- .../api/v1alpha1/userresourcestatus.go | 21 ++++-- .../applyconfiguration/internal/internal.go | 3 + test/apivalidations/user_test.go | 4 +- website/docs/crd-reference.md | 1 + 13 files changed, 126 insertions(+), 13 deletions(-) diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index ddccb96e2..38acfc92d 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -46,7 +46,6 @@ type UserResourceSpec struct { // passwordRef is a reference to a Secret containing the password // for this user. The Secret must contain a key named "password". // +required - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="passwordRef is immutable" PasswordRef KubernetesNameRef `json:"passwordRef,omitempty"` } @@ -92,4 +91,10 @@ type UserResourceStatus struct { // +kubebuilder:validation:MaxLength:=1024 // +optional PasswordExpiresAt string `json:"passwordExpiresAt,omitempty"` + + // appliedPasswordRef is the name of the Secret containing the + // password that was last applied to the OpenStack resource. + // +kubebuilder:validation:MaxLength=1024 + // +optional + AppliedPasswordRef string `json:"appliedPasswordRef,omitempty"` } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index e13e9899b..6190bb940 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -11456,6 +11456,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref Format: "", }, }, + "appliedPasswordRef": { + SchemaProps: spec.SchemaProps{ + Description: "appliedPasswordRef is the name of the Secret containing the password that was last applied to the OpenStack resource.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml index 201bb5eba..90bc76760 100644 --- a/config/crd/bases/openstack.k-orc.cloud_users.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -193,9 +193,6 @@ spec: maxLength: 253 minLength: 1 type: string - x-kubernetes-validations: - - message: passwordRef is immutable - rule: self == oldSelf required: - passwordRef type: object @@ -302,6 +299,12 @@ spec: description: resource contains the observed state of the OpenStack resource. properties: + appliedPasswordRef: + description: |- + appliedPasswordRef is the name of the Secret containing the + password that was last applied to the OpenStack resource. + maxLength: 1024 + type: string defaultProjectID: description: defaultProjectID is the ID of the Default Project to which the user is associated with. diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index 4b4683fe5..b556a6f80 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -22,6 +22,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -31,8 +32,10 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/applyconfigs" "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" + orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" ) // OpenStack resource types @@ -183,6 +186,75 @@ func (actuator userActuator) DeleteResource(ctx context.Context, _ orcObjectPT, return progress.WrapError(actuator.osClient.DeleteUser(ctx, resource.ID)) } +func (actuator userActuator) reconcilePassword(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + return nil + } + + currentRef := string(resource.PasswordRef) + var lastAppliedRef string + if obj.Status.Resource != nil { + lastAppliedRef = obj.Status.Resource.AppliedPasswordRef + } + + if lastAppliedRef == currentRef { + return nil + } + + // Read the password from the referenced Secret + secret, secretRS := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + &resource.PasswordRef, "Secret", + func(*corev1.Secret) bool { return true }, + ) + if secretRS != nil { + return secretRS + } + + passwordBytes, ok := secret.Data["password"] + if !ok { + return progress.NewReconcileStatus().WithProgressMessage("Password secret does not contain \"password\" key") + } + password := string(passwordBytes) + + // Only call UpdateUser if this is not the first reconcile after creation. + // CreateResource already set the initial password. + if lastAppliedRef != "" { + log.V(logging.Info).Info("Updating password") + _, err := actuator.osClient.UpdateUser(ctx, osResource.ID, users.UpdateOpts{ + Password: password, + }) + + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + if err != nil { + return progress.WrapError(err) + } + } + + // Update the lastAppliedPasswordRef status field via a MergePatch. + // MergePatch sets only the specified fields without claiming SSA + // ownership, so the main SSA status update won't remove this field. + statusApply := orcapplyconfigv1alpha1.UserResourceStatus(). + WithAppliedPasswordRef(currentRef) + applyConfig := orcapplyconfigv1alpha1.User(obj.Name, obj.Namespace). + WithUID(obj.UID). + WithStatus(orcapplyconfigv1alpha1.UserStatus(). + WithResource(statusApply)) + if err := actuator.k8sClient.Status().Patch(ctx, obj, + applyconfigs.Patch(types.MergePatchType, applyConfig)); err != nil { + return progress.WrapError(err) + } + + if lastAppliedRef != "" { + return progress.NeedsRefresh() + } + return nil +} + func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { log := ctrl.LoggerFrom(ctx) resource := obj.Spec.Resource @@ -259,6 +331,7 @@ func handleEnabledUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT, func (actuator userActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ + actuator.reconcilePassword, actuator.updateResource, }, nil } diff --git a/internal/controllers/user/tests/user-update/00-assert.yaml b/internal/controllers/user/tests/user-update/00-assert.yaml index c7a2749fc..e30fd7137 100644 --- a/internal/controllers/user/tests/user-update/00-assert.yaml +++ b/internal/controllers/user/tests/user-update/00-assert.yaml @@ -12,6 +12,7 @@ assertAll: - celExpr: "!has(user.status.resource.defaultProjectID)" # passwordExpiresAt depends on the Keystone security_compliance # configuration and is not asserted here. + - celExpr: "user.status.resource.appliedPasswordRef == 'user-update'" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User diff --git a/internal/controllers/user/tests/user-update/01-assert.yaml b/internal/controllers/user/tests/user-update/01-assert.yaml index 0a9fa6937..cf594b6ee 100644 --- a/internal/controllers/user/tests/user-update/01-assert.yaml +++ b/internal/controllers/user/tests/user-update/01-assert.yaml @@ -8,6 +8,7 @@ status: name: user-update-updated description: user-update-updated enabled: false + appliedPasswordRef: user-update-password-updated conditions: - type: Available status: "True" diff --git a/internal/controllers/user/tests/user-update/01-updated-resource.yaml b/internal/controllers/user/tests/user-update/01-updated-resource.yaml index dea4f5476..dd7727629 100644 --- a/internal/controllers/user/tests/user-update/01-updated-resource.yaml +++ b/internal/controllers/user/tests/user-update/01-updated-resource.yaml @@ -1,4 +1,12 @@ --- +apiVersion: v1 +kind: Secret +metadata: + name: user-update-password-updated +type: Opaque +stringData: + password: "TestPasswordUpdated" +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -8,3 +16,4 @@ spec: name: user-update-updated description: user-update-updated enabled: false + passwordRef: user-update-password-updated diff --git a/internal/controllers/user/tests/user-update/02-assert.yaml b/internal/controllers/user/tests/user-update/02-assert.yaml index c2c14d837..7682f1636 100644 --- a/internal/controllers/user/tests/user-update/02-assert.yaml +++ b/internal/controllers/user/tests/user-update/02-assert.yaml @@ -10,6 +10,7 @@ assertAll: - celExpr: "!has(user.status.resource.description)" # passwordExpiresAt depends on the Keystone security_compliance # configuration and is not asserted here. + - celExpr: "user.status.resource.appliedPasswordRef == 'user-update'" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User diff --git a/internal/controllers/user/tests/user-update/README.md b/internal/controllers/user/tests/user-update/README.md index 13da45548..160b0122d 100644 --- a/internal/controllers/user/tests/user-update/README.md +++ b/internal/controllers/user/tests/user-update/README.md @@ -6,7 +6,7 @@ Create a User using only mandatory fields. ## Step 01 -Update all mutable fields. +Update all mutable fields, including passwordRef (pointing to a new Secret). ## Step 02 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go index c23b0b6cf..db56adfbf 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go @@ -21,12 +21,13 @@ package v1alpha1 // UserResourceStatusApplyConfiguration represents a declarative configuration of the UserResourceStatus type for use // with apply. type UserResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - DomainID *string `json:"domainID,omitempty"` - DefaultProjectID *string `json:"defaultProjectID,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - PasswordExpiresAt *string `json:"passwordExpiresAt,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DomainID *string `json:"domainID,omitempty"` + DefaultProjectID *string `json:"defaultProjectID,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + PasswordExpiresAt *string `json:"passwordExpiresAt,omitempty"` + AppliedPasswordRef *string `json:"appliedPasswordRef,omitempty"` } // UserResourceStatusApplyConfiguration constructs a declarative configuration of the UserResourceStatus type for use with @@ -82,3 +83,11 @@ func (b *UserResourceStatusApplyConfiguration) WithPasswordExpiresAt(value strin b.PasswordExpiresAt = &value return b } + +// WithAppliedPasswordRef sets the AppliedPasswordRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AppliedPasswordRef field is set to the value of the last call. +func (b *UserResourceStatusApplyConfiguration) WithAppliedPasswordRef(value string) *UserResourceStatusApplyConfiguration { + b.AppliedPasswordRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index c641e66f4..84688dc8a 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3415,6 +3415,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus map: fields: + - name: appliedPasswordRef + type: + scalar: string - name: defaultProjectID type: scalar: string diff --git a/test/apivalidations/user_test.go b/test/apivalidations/user_test.go index 15057eb85..f38671d01 100644 --- a/test/apivalidations/user_test.go +++ b/test/apivalidations/user_test.go @@ -120,7 +120,7 @@ var _ = Describe("ORC User API validations", func() { Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("defaultProjectRef is immutable"))) }) - It("should have immutable passwordRef", func(ctx context.Context) { + It("should have mutable passwordRef", func(ctx context.Context) { user := userStub(namespace) patch := baseUserPatch(user) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). @@ -129,6 +129,6 @@ var _ = Describe("ORC User API validations", func() { patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). WithPasswordRef("password-b")) - Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("passwordRef is immutable"))) + Expect(applyObj(ctx, user, patch)).To(Succeed()) }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 07dbfbe88..9d15a005a 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -4458,6 +4458,7 @@ _Appears in:_ | `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
Optional: \{\}
| | `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
| | `passwordExpiresAt` _string_ | passwordExpiresAt is the timestamp at which the user's password expires. | | MaxLength: 1024
Optional: \{\}
| +| `appliedPasswordRef` _string_ | appliedPasswordRef is the name of the Secret containing the
password that was last applied to the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| #### UserSpec From 40ccd100f583cca3179678d0fb64477d003745dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:43:33 +0000 Subject: [PATCH 120/121] :seedling:(deps): bump actions/setup-go in the all-github-actions group Bumps the all-github-actions group with 1 update: [actions/setup-go](https://github.com/actions/setup-go). Updates `actions/setup-go` from 6.3.0 to 6.4.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/4b73464bb391d4059bd26b0524d20df3927bd417...4a3601121dd01d1626a1e23e37211e3254c1c06c) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/go-lint.yaml | 2 +- .github/workflows/pr-dependabot.yaml | 2 +- .github/workflows/semver.yaml | 2 +- .github/workflows/unit.yml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index 5a520c6b8..80def4091 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -22,7 +22,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index 4cd85de01..7126e9e82 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -26,7 +26,7 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 with: go-version: ${{ steps.vars.outputs.go_version }} - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # tag=v5.0.4 diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 6b846336e..42c5d6a8c 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -33,7 +33,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 4dca40c4e..8f53485e3 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -26,7 +26,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 497198df9..92a733078 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -26,7 +26,7 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # tag=v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 with: go-version: ${{ steps.vars.outputs.go_version }} - name: Run verify security target From 5dc3f404020d5d6c4e155fe72a3bc2f6f475b00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 30 Mar 2026 13:27:11 +0200 Subject: [PATCH 121/121] user: make passwordRef optional Keystone allows creating passwordless users for authentication via federation, application credentials, or other means. Make passwordRef optional so ORC can create users without a password. A CEL validation on UserResourceSpec prevents removing passwordRef once set, since Keystone does not support clearing a password via the API. The field remains mutable (can be changed to a different Secret). --- api/v1alpha1/user_types.go | 6 +++-- api/v1alpha1/zz_generated.deepcopy.go | 5 ++++ cmd/models-schema/zz_generated.openapi.go | 3 +-- .../bases/openstack.k-orc.cloud_users.yaml | 6 +++-- internal/controllers/user/actuator.go | 10 +++---- internal/controllers/user/controller.go | 4 +-- .../00-create-resource.yaml | 11 +------- .../user/tests/user-create-minimal/README.md | 2 +- .../00-import-resource.yaml | 8 ------ .../01-create-trap-resource.yaml | 3 +-- .../02-create-resource.yaml | 3 +-- .../00-create-resources.yaml | 12 +-------- .../tests/user-import/00-import-resource.yaml | 8 ------ .../user-import/01-create-trap-resource.yaml | 3 +-- .../tests/user-import/02-create-resource.yaml | 3 +-- test/apivalidations/user_test.go | 26 ++++++++++++++----- website/docs/crd-reference.md | 2 +- 17 files changed, 49 insertions(+), 66 deletions(-) diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index 38acfc92d..e085006d9 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 // UserResourceSpec contains the desired state of the resource. +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.passwordRef) || has(self.passwordRef)",message="passwordRef may not be removed once set" type UserResourceSpec struct { // name will be the name of the created resource. If not specified, the // name of the ORC object will be used. @@ -45,8 +46,9 @@ type UserResourceSpec struct { // passwordRef is a reference to a Secret containing the password // for this user. The Secret must contain a key named "password". - // +required - PasswordRef KubernetesNameRef `json:"passwordRef,omitempty"` + // If not specified, the user is created without a password. + // +optional + PasswordRef *KubernetesNameRef `json:"passwordRef,omitempty"` } // UserFilter defines an existing resource by its properties diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4bd75eca4..dffa3ec73 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5924,6 +5924,11 @@ func (in *UserResourceSpec) DeepCopyInto(out *UserResourceSpec) { *out = new(bool) **out = **in } + if in.PasswordRef != nil { + in, out := &in.PasswordRef, &out.PasswordRef + *out = new(KubernetesNameRef) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceSpec. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 6190bb940..2c1ee063e 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -11395,13 +11395,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref c }, "passwordRef": { SchemaProps: spec.SchemaProps{ - Description: "passwordRef is a reference to a Secret containing the password for this user. The Secret must contain a key named \"password\".", + Description: "passwordRef is a reference to a Secret containing the password for this user. The Secret must contain a key named \"password\". If not specified, the user is created without a password.", Type: []string{"string"}, Format: "", }, }, }, - Required: []string{"passwordRef"}, }, }, } diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml index 90bc76760..e9dc0fa8c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_users.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -190,12 +190,14 @@ spec: description: |- passwordRef is a reference to a Secret containing the password for this user. The Secret must contain a key named "password". + If not specified, the user is created without a password. maxLength: 253 minLength: 1 type: string - required: - - passwordRef type: object + x-kubernetes-validations: + - message: passwordRef may not be removed once set + rule: '!has(oldSelf.passwordRef) || has(self.passwordRef)' required: - cloudCredentialsRef type: object diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index b556a6f80..d82014cab 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -140,10 +140,10 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT } var password string - { + if resource.PasswordRef != nil { secret, secretReconcileStatus := dependency.FetchDependency( ctx, actuator.k8sClient, obj.Namespace, - &resource.PasswordRef, "Secret", + resource.PasswordRef, "Secret", func(*corev1.Secret) bool { return true }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus) @@ -189,11 +189,11 @@ func (actuator userActuator) DeleteResource(ctx context.Context, _ orcObjectPT, func (actuator userActuator) reconcilePassword(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { log := ctrl.LoggerFrom(ctx) resource := obj.Spec.Resource - if resource == nil { + if resource == nil || resource.PasswordRef == nil { return nil } - currentRef := string(resource.PasswordRef) + currentRef := string(*resource.PasswordRef) var lastAppliedRef string if obj.Status.Resource != nil { lastAppliedRef = obj.Status.Resource.AppliedPasswordRef @@ -206,7 +206,7 @@ func (actuator userActuator) reconcilePassword(ctx context.Context, obj orcObjec // Read the password from the referenced Secret secret, secretRS := dependency.FetchDependency( ctx, actuator.k8sClient, obj.Namespace, - &resource.PasswordRef, "Secret", + resource.PasswordRef, "Secret", func(*corev1.Secret) bool { return true }, ) if secretRS != nil { diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go index f69818726..e0f106927 100644 --- a/internal/controllers/user/controller.go +++ b/internal/controllers/user/controller.go @@ -91,10 +91,10 @@ var passwordDependency = dependency.NewDependency[*orcv1alpha1.UserList, *corev1 "spec.resource.passwordRef", func(user *orcv1alpha1.User) []string { resource := user.Spec.Resource - if resource == nil { + if resource == nil || resource.PasswordRef == nil { return nil } - return []string{string(resource.PasswordRef)} + return []string{string(*resource.PasswordRef)} }, ) diff --git a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml index 72545e48c..c3d2147bf 100644 --- a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml +++ b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml @@ -1,12 +1,4 @@ --- -apiVersion: v1 -kind: Secret -metadata: - name: user-create-minimal -type: Opaque -stringData: - password: "TestPassword" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -16,5 +8,4 @@ spec: cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - resource: - passwordRef: user-create-minimal \ No newline at end of file + resource: {} \ No newline at end of file diff --git a/internal/controllers/user/tests/user-create-minimal/README.md b/internal/controllers/user/tests/user-create-minimal/README.md index 4d3dd61ff..548da3f08 100644 --- a/internal/controllers/user/tests/user-create-minimal/README.md +++ b/internal/controllers/user/tests/user-create-minimal/README.md @@ -2,7 +2,7 @@ ## Step 00 -Create a minimal User, that sets only the required fields, and verify that the observed state corresponds to the spec. +Create a minimal User without a password, 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. diff --git a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml index 6001315f4..0681c805b 100644 --- a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml @@ -1,12 +1,4 @@ --- -apiVersion: v1 -kind: Secret -metadata: - name: user-import-dependency-password -type: Opaque -stringData: - password: "TestPassword" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain metadata: diff --git a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml index 48536462a..7154af7ba 100644 --- a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml @@ -21,5 +21,4 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - domainRef: user-import-dependency-not-this-one - passwordRef: user-import-dependency-password \ No newline at end of file + domainRef: user-import-dependency-not-this-one \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml index 51c32bb06..ea64cab75 100644 --- a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml +++ b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml @@ -20,5 +20,4 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - domainRef: user-import-dependency-external - passwordRef: user-import-dependency-password \ No newline at end of file + domainRef: user-import-dependency-external \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml index 10e809d1a..498801786 100644 --- a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml +++ b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml @@ -10,14 +10,6 @@ spec: managementPolicy: managed resource: {} --- -apiVersion: v1 -kind: Secret -metadata: - name: user-import-error-password -type: Opaque -stringData: - password: "TestPassword" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: @@ -30,7 +22,6 @@ spec: resource: description: User from "import error" test domainRef: user-import-error-domain - passwordRef: user-import-error-password --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -43,5 +34,4 @@ spec: managementPolicy: managed resource: description: User from "import error" test - domainRef: user-import-error-domain - passwordRef: user-import-error-password \ No newline at end of file + domainRef: user-import-error-domain \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/00-import-resource.yaml b/internal/controllers/user/tests/user-import/00-import-resource.yaml index a80dc427a..d8b7199fa 100644 --- a/internal/controllers/user/tests/user-import/00-import-resource.yaml +++ b/internal/controllers/user/tests/user-import/00-import-resource.yaml @@ -1,12 +1,4 @@ --- -apiVersion: v1 -kind: Secret -metadata: - name: user-import-password -type: Opaque -stringData: - password: "TestPassword" ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Domain metadata: diff --git a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml index 18ae8d89a..ea393341f 100644 --- a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml +++ b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml @@ -13,5 +13,4 @@ spec: managementPolicy: managed resource: description: User user-import-external from "user-import" test - domainRef: user-import-external - passwordRef: user-import-password \ No newline at end of file + domainRef: user-import-external \ No newline at end of file diff --git a/internal/controllers/user/tests/user-import/02-create-resource.yaml b/internal/controllers/user/tests/user-import/02-create-resource.yaml index ca3cb03fc..43a43ef04 100644 --- a/internal/controllers/user/tests/user-import/02-create-resource.yaml +++ b/internal/controllers/user/tests/user-import/02-create-resource.yaml @@ -10,5 +10,4 @@ spec: managementPolicy: managed resource: description: User user-import-external from "user-import" test - domainRef: user-import-external - passwordRef: user-import-password \ No newline at end of file + domainRef: user-import-external \ No newline at end of file diff --git a/test/apivalidations/user_test.go b/test/apivalidations/user_test.go index f38671d01..983c778ab 100644 --- a/test/apivalidations/user_test.go +++ b/test/apivalidations/user_test.go @@ -41,8 +41,7 @@ func userStub(namespace *corev1.Namespace) *orcv1alpha1.User { } func testUserResource() *applyconfigv1alpha1.UserResourceSpecApplyConfiguration { - return applyconfigv1alpha1.UserResourceSpec(). - WithPasswordRef("user-password") + return applyconfigv1alpha1.UserResourceSpec() } func baseUserPatch(user client.Object) *applyconfigv1alpha1.UserApplyConfiguration { @@ -96,12 +95,10 @@ var _ = Describe("ORC User API validations", func() { user := userStub(namespace) patch := baseUserPatch(user) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). - WithPasswordRef("user-password"). WithDomainRef("domain-a")) Expect(applyObj(ctx, user, patch)).To(Succeed()) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). - WithPasswordRef("user-password"). WithDomainRef("domain-b")) Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) }) @@ -110,16 +107,21 @@ var _ = Describe("ORC User API validations", func() { user := userStub(namespace) patch := baseUserPatch(user) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). - WithPasswordRef("user-password"). WithDefaultProjectRef("project-a")) Expect(applyObj(ctx, user, patch)).To(Succeed()) patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). - WithPasswordRef("user-password"). WithDefaultProjectRef("project-b")) Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("defaultProjectRef is immutable"))) }) + It("should allow omitting passwordRef", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec()) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + }) + It("should have mutable passwordRef", func(ctx context.Context) { user := userStub(namespace) patch := baseUserPatch(user) @@ -131,4 +133,16 @@ var _ = Describe("ORC User API validations", func() { WithPasswordRef("password-b")) Expect(applyObj(ctx, user, patch)).To(Succeed()) }) + + It("should not allow removing passwordRef once set", func(ctx context.Context) { + user := userStub(namespace) + patch := baseUserPatch(user) + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithPasswordRef("password-a")) + Expect(applyObj(ctx, user, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec(). + WithDescription("updated")) + Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("passwordRef may not be removed once set"))) + }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 9d15a005a..aee5bb6d7 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -4436,7 +4436,7 @@ _Appears in:_ | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
| -| `passwordRef` _[KubernetesNameRef](#kubernetesnameref)_ | passwordRef is a reference to a Secret containing the password
for this user. The Secret must contain a key named "password". | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `passwordRef` _[KubernetesNameRef](#kubernetesnameref)_ | passwordRef is a reference to a Secret containing the password
for this user. The Secret must contain a key named "password".
If not specified, the user is created without a password. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| #### UserResourceStatus