From b094882bd9675d318b0cabb5430c97d9771c50a5 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Thu, 18 Dec 2025 11:59:59 -0300 Subject: [PATCH 001/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] :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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] :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/237] :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/237] 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/237] :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/237] 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/237] 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/237] 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/237] :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/237] :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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] :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/237] :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/237] 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/237] 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/237] 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/237] :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/237] :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/237] 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/237] :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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] :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/237] :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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] 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/237] :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/237] 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/237] 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/237] :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/237] 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 From 5bc9cf7f6867600e74132daf1e4ad73cda17d824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 1 Apr 2026 14:47:25 +0200 Subject: [PATCH 122/237] role: include DomainID in adoption listing for domain-scoped roles Keystone's GET /v3/roles API implicitly filters to domain_id=NULL (global roles only) when no domain_id parameter is provided. This means domain-scoped roles are invisible to the adoption check in ListOSResourcesForAdoption, which only filtered by name. If a stale informer cache read causes the controller to re-enter the create path after a successful creation, the adoption mechanism fails to find the just-created domain-scoped role, resulting in a duplicate POST and a terminal 409 Conflict error. Fix by resolving the domain dependency and including DomainID in the roles.ListOpts when domainRef is set. --- internal/controllers/role/actuator.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/controllers/role/actuator.go b/internal/controllers/role/actuator.go index ba3be6b75..1d5be1885 100644 --- a/internal/controllers/role/actuator.go +++ b/internal/controllers/role/actuator.go @@ -75,6 +75,18 @@ func (actuator roleActuator) ListOSResourcesForAdoption(ctx context.Context, orc Name: getResourceName(orcObject), } + if resourceSpec.DomainRef != nil { + domain, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if domain.Status.ID != nil { + listOpts.DomainID = *domain.Status.ID + } + } + return actuator.osClient.ListRoles(ctx, listOpts), true } From ec11043024f0a6411964333f7a86c7552f1d850b Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Wed, 1 Apr 2026 09:36:02 +0100 Subject: [PATCH 123/237] Mutability tests for Keystone's User Resource TestHandleEnableUpdate - Compares desired state (K8s) vs actual state (openstack) TestReconcilePassword - Uses mock openstack client and fake k8s client to verify the functions reconciliation logic that passwordReconcile reads passwords from secrets correctly, only calls UpdateUser when actually needed and tracks what password was last applied Signed-off-by: Daniel Lawton --- internal/controllers/user/actuator_test.go | 204 ++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/internal/controllers/user/actuator_test.go b/internal/controllers/user/actuator_test.go index 2543da933..aefc193da 100644 --- a/internal/controllers/user/actuator_test.go +++ b/internal/controllers/user/actuator_test.go @@ -17,11 +17,20 @@ limitations under the License. package user import ( + "context" "testing" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" - orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients/mock" ) func TestNeedsUpdate(t *testing.T) { @@ -117,3 +126,196 @@ func TestHandleDescriptionUpdate(t *testing.T) { } } + +func TestHandleEnabledUpdate(t *testing.T) { + ptrToBool := ptr.To[bool] + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Identical", newValue: ptrToBool(true), existingValue: true, expectChange: false}, + {name: "Different", newValue: ptrToBool(true), existingValue: false, expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: false, expectChange: true}, + {name: "No value provided, existing is default", newValue: nil, existingValue: true, expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.UserResourceSpec{Enabled: tt.newValue} + osResource := &users.User{Enabled: tt.existingValue} + + updateOpts := users.UpdateOpts{} + handleEnabledUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestReconcilePassword(t *testing.T) { + ptrToPasswordRef := ptr.To[orcv1alpha1.KubernetesNameRef] + testCases := []struct { + name string + orcObject *orcv1alpha1.User + osResource *users.User + secret *corev1.Secret + setupMock func(*mock.MockUserClientMockRecorder) + wantReschedule bool + wantErr bool + }{ + { + name: "No password ref set", + orcObject: &orcv1alpha1.User{ + Spec: orcv1alpha1.UserSpec{ + Resource: &orcv1alpha1.UserResourceSpec{}, + }, + }, + osResource: &users.User{ID: "user-id"}, + wantReschedule: false, + wantErr: false, + }, + { + name: "Resource is nil", + orcObject: &orcv1alpha1.User{ + Spec: orcv1alpha1.UserSpec{}, + }, + osResource: &users.User{ID: "user-id"}, + wantReschedule: false, + wantErr: false, + }, + { + name: "Password ref unchanged", + orcObject: &orcv1alpha1.User{ + Spec: orcv1alpha1.UserSpec{ + Resource: &orcv1alpha1.UserResourceSpec{ + PasswordRef: ptrToPasswordRef("my-secret"), + }, + }, + Status: orcv1alpha1.UserStatus{ + Resource: &orcv1alpha1.UserResourceStatus{ + AppliedPasswordRef: "my-secret", + }, + }, + }, + osResource: &users.User{ID: "user-id"}, + wantReschedule: false, + wantErr: false, + }, + { + name: "First password set - no UpdateUser call", + orcObject: &orcv1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-user", + Namespace: "test-ns", + UID: "test-uid", + }, + Spec: orcv1alpha1.UserSpec{ + Resource: &orcv1alpha1.UserResourceSpec{ + PasswordRef: ptrToPasswordRef("my-secret"), + }, + }, + Status: orcv1alpha1.UserStatus{ + Resource: &orcv1alpha1.UserResourceStatus{ + AppliedPasswordRef: "", + }, + }, + }, + osResource: &users.User{ID: "user-id"}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-secret", + Namespace: "test-ns", + }, + Data: map[string][]byte{ + "password": []byte("mypassword123"), + }, + }, + // No UpdateUser call expected on first reconcile + setupMock: func(recorder *mock.MockUserClientMockRecorder) {}, + wantReschedule: false, + wantErr: false, + }, + { + name: "Password changed - UpdateUser called", + orcObject: &orcv1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-user", + Namespace: "test-ns", + UID: "test-uid", + }, + Spec: orcv1alpha1.UserSpec{ + Resource: &orcv1alpha1.UserResourceSpec{ + PasswordRef: ptrToPasswordRef("my-secret"), + }, + }, + Status: orcv1alpha1.UserStatus{ + Resource: &orcv1alpha1.UserResourceStatus{ + AppliedPasswordRef: "old-secret", + }, + }, + }, + osResource: &users.User{ID: "user-id"}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-secret", + Namespace: "test-ns", + }, + Data: map[string][]byte{ + "password": []byte("newpassword456"), + }, + }, + setupMock: func(recorder *mock.MockUserClientMockRecorder) { + recorder.UpdateUser(gomock.Any(), "user-id", gomock.Any()).Return(&users.User{}, nil) + }, + wantReschedule: true, // NeedsRefresh returns true + wantErr: false, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + mockctrl := gomock.NewController(t) + userClient := mock.NewMockUserClient(mockctrl) + + // Create fake k8s client + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = orcv1alpha1.AddToScheme(scheme) + + objects := []client.Object{tt.orcObject} + if tt.secret != nil { + objects = append(objects, tt.secret) + } + + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(objects...). + WithStatusSubresource(&orcv1alpha1.User{}). + Build() + + actuator := userActuator{ + osClient: userClient, + k8sClient: k8sClient, + } + + if tt.setupMock != nil { + tt.setupMock(userClient.EXPECT()) + } + + reconcileStatus := actuator.reconcilePassword(context.TODO(), tt.orcObject, tt.osResource) + + needsReschedule, err := reconcileStatus.NeedsReschedule() + if (err != nil) != tt.wantErr { + t.Errorf("reconcilePassword() error = %v, wantErr %v", err, tt.wantErr) + } + if needsReschedule != tt.wantReschedule { + t.Errorf("reconcilePassword() needsReschedule = %v, want %v", needsReschedule, tt.wantReschedule) + } + }) + } +} From ddc2fdc6946bb0650d92b0b8efa0f6f28c9bbedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Apr 2026 14:48:09 +0200 Subject: [PATCH 124/237] Set commit image expiration via Quay API The `--annotation quay.expires-after=4w` flag does not work for multi-arch images because Quay only reads Docker image config labels, not OCI manifest annotations. Since multi-arch image indexes have no config, neither labels nor annotations reach Quay's expiration logic. Replace it with a Quay REST API call to set a 4-week expiration on the commit tag after pushing. --- .github/workflows/container_image.yaml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index a60a80586..0673adb5f 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -27,9 +27,21 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4 - - run: | + - name: Build and push images + run: | 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 }} && \ - make docker-buildx IMG=${{ env.image_tag_commit }} DOCKER_BUILD_ARGS="--annotation quay.expires-after=4w" + make docker-buildx IMG=${{ env.image_tag_commit }} + + - name: Set expiration on commit image + env: + QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }} # zizmor: ignore[secrets-outside-env] + run: | + EXPIRATION=$(($(date -u +%s) + 2419200)) + curl -sf -X PUT \ + -H "Authorization: Bearer ${QUAY_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"expiration\": $EXPIRATION}" \ + "https://quay.io/api/v1/repository/orc/openstack-resource-controller/tag/commit-${GITHUB_SHA::7}" From f4a9e21169e399422f0651ae9dacaefbe1bfa9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Apr 2026 16:37:23 +0200 Subject: [PATCH 125/237] Use oauth token for accessing quay API We need a separate OAUTH token to access quay's API, that is different from the account password. --- .github/workflows/container_image.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index 0673adb5f..5a6911304 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -37,11 +37,11 @@ jobs: - name: Set expiration on commit image env: - QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }} # zizmor: ignore[secrets-outside-env] + QUAY_OAUTH_TOKEN: ${{ secrets.QUAY_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env] run: | EXPIRATION=$(($(date -u +%s) + 2419200)) curl -sf -X PUT \ - -H "Authorization: Bearer ${QUAY_TOKEN}" \ + -H "Authorization: Bearer ${QUAY_OAUTH_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"expiration\": $EXPIRATION}" \ "https://quay.io/api/v1/repository/orc/openstack-resource-controller/tag/commit-${GITHUB_SHA::7}" From e60a897345dc1b6f5a34578bc1cdb5906087758d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:20:00 +0000 Subject: [PATCH 126/237] :seedling:(deps): bump kiegroup/git-backporting Bumps the all-github-actions group with 1 update: [kiegroup/git-backporting](https://github.com/kiegroup/git-backporting). Updates `kiegroup/git-backporting` from 4.9.0 to 4.9.1 - [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/82e45d73f8d39bc3d7eb4b41859d313696c93ed9...08da0b07ef2330d189f6074ec8db736b3aa9f465) --- updated-dependencies: - dependency-name: kiegroup/git-backporting dependency-version: 4.9.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/backport.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 713212176..1c8173a88 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -38,7 +38,7 @@ jobs: if: > contains(github.event.pull_request.labels.*.name, 'semver:patch') || contains(github.event.label.name, 'semver:patch') - uses: kiegroup/git-backporting@82e45d73f8d39bc3d7eb4b41859d313696c93ed9 # tag=v4.9.0 + uses: kiegroup/git-backporting@08da0b07ef2330d189f6074ec8db736b3aa9f465 # tag=v4.9.1 with: target-branch: release-1.0 pull-request: ${{ github.event.pull_request.url }} From 392b9054788280ac10b8b378c3c5dc030bab729c Mon Sep 17 00:00:00 2001 From: Gondermann Date: Tue, 10 Mar 2026 15:59:57 +0100 Subject: [PATCH 127/237] Scaffolding for the ApplicationCredential controller $ go run ./cmd/scaffold-controller -interactive=false \ -kind=ApplicationCredential \ -gophercloud-client=NewIdentityV3 \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials \ -gophercloud-type=ApplicationCredential \ -openstack-json-object=application_credentials \ -required-create-dependency=User \ -import-dependency=User On-behalf-of: SAP nils.gondermann@sap.com --- api/v1alpha1/applicationcredential_types.go | 88 +++ api/v1alpha1/zz_generated.deepcopy.go | 70 +++ cmd/models-schema/zz_generated.openapi.go | 564 +++++++++++------- config/rbac/role.yaml | 2 + ...nstack_v1alpha1_applicationcredential.yaml | 14 + .../applicationcredential/actuator.go | 271 +++++++++ .../applicationcredential/actuator_test.go | 119 ++++ .../applicationcredential/controller.go | 114 ++++ .../applicationcredential/status.go | 64 ++ .../00-assert.yaml | 33 + .../00-create-resource.yaml | 29 + .../00-secret.yaml | 6 + .../README.md | 11 + .../00-assert.yaml | 32 + .../00-create-resource.yaml | 28 + .../00-secret.yaml | 6 + .../01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../README.md | 15 + .../00-assert.yaml | 30 + .../00-create-resources-missing-deps.yaml | 42 ++ .../00-secret.yaml | 6 + .../01-assert.yaml | 30 + .../01-create-dependencies.yaml | 19 + .../02-assert.yaml | 17 + .../02-delete-dependencies.yaml | 9 + .../03-assert.yaml | 9 + .../03-delete-resources.yaml | 10 + .../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 + .../README.md | 29 + .../00-assert.yaml | 30 + .../00-create-resources.yaml | 43 ++ .../00-secret.yaml | 6 + .../01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../README.md | 13 + .../00-assert.yaml | 15 + .../00-import-resource.yaml | 15 + .../00-secret.yaml | 6 + .../01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 31 + .../02-assert.yaml | 33 + .../02-create-resource.yaml | 28 + .../applicationcredential-import/README.md | 18 + .../00-assert.yaml | 26 + .../00-minimal-resource.yaml | 28 + .../00-secret.yaml | 6 + .../01-assert.yaml | 17 + .../01-updated-resource.yaml | 10 + .../02-assert.yaml | 26 + .../02-reverted-resource.yaml | 7 + .../applicationcredential-update/README.md | 17 + internal/osclients/applicationcredential.go | 104 ++++ .../applicationcredential_test.go | 128 ++++ website/docs/crd-reference.md | 10 + 66 files changed, 2352 insertions(+), 229 deletions(-) create mode 100644 api/v1alpha1/applicationcredential_types.go create mode 100644 config/samples/openstack_v1alpha1_applicationcredential.yaml create mode 100644 internal/controllers/applicationcredential/actuator.go create mode 100644 internal/controllers/applicationcredential/actuator_test.go create mode 100644 internal/controllers/applicationcredential/controller.go create mode 100644 internal/controllers/applicationcredential/status.go create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-full/README.md create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/README.md create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/02-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-dependency/README.md create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/README.md create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import-error/README.md create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-import/README.md create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/01-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml create mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/README.md create mode 100644 internal/osclients/applicationcredential.go create mode 100644 test/apivalidations/applicationcredential_test.go diff --git a/api/v1alpha1/applicationcredential_types.go b/api/v1alpha1/applicationcredential_types.go new file mode 100644 index 000000000..07e3889cc --- /dev/null +++ b/api/v1alpha1/applicationcredential_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 + +// ApplicationCredentialResourceSpec contains the desired state of the resource. +type ApplicationCredentialResourceSpec 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"` + + // userRef is a reference to the ORC User which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable" + UserRef KubernetesNameRef `json:"userRef,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/applicationcredentials + // + // 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"` +} + +// ApplicationCredentialFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type ApplicationCredentialFilter 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"` + + // userRef is a reference to the ORC User which this resource is associated with. + // +optional + UserRef *KubernetesNameRef `json:"userRef,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/applicationcredentials +} + +// ApplicationCredentialResourceStatus represents the observed state of the resource. +type ApplicationCredentialResourceStatus 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"` + + // userID is the ID of the User to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + UserID string `json:"userID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ApplicationCredential structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index dffa3ec73..3d16b852d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -337,6 +337,76 @@ func (in *AllowedAddressPairStatus) DeepCopy() *AllowedAddressPairStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialFilter) DeepCopyInto(out *ApplicationCredentialFilter) { + *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.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialFilter. +func (in *ApplicationCredentialFilter) DeepCopy() *ApplicationCredentialFilter { + if in == nil { + return nil + } + out := new(ApplicationCredentialFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialResourceSpec) DeepCopyInto(out *ApplicationCredentialResourceSpec) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialResourceSpec. +func (in *ApplicationCredentialResourceSpec) DeepCopy() *ApplicationCredentialResourceSpec { + if in == nil { + return nil + } + out := new(ApplicationCredentialResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialResourceStatus) DeepCopyInto(out *ApplicationCredentialResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialResourceStatus. +func (in *ApplicationCredentialResourceStatus) DeepCopy() *ApplicationCredentialResourceStatus { + if in == nil { + return nil + } + out := new(ApplicationCredentialResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudCredentialsReference) DeepCopyInto(out *CloudCredentialsReference) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 2c1ee063e..15a4875ed 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -30,235 +30,238 @@ 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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference": schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Domain": schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainFilter": schema_openstack_resource_controller_v2_api_v1alpha1_DomainFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainImport": schema_openstack_resource_controller_v2_api_v1alpha1_DomainImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainList": schema_openstack_resource_controller_v2_api_v1alpha1_DomainList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_DomainResourceSpec(ref), - "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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByNeutronTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByNeutronTags(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByServerTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByServerTags(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FixedIPStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Flavor": schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorList": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIP": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIP(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPImport": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPList": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Group": schema_openstack_resource_controller_v2_api_v1alpha1_Group(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_GroupFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_GroupImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupList": schema_openstack_resource_controller_v2_api_v1alpha1_GroupList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_GroupResourceSpec(ref), - "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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Image": schema_openstack_resource_controller_v2_api_v1alpha1_Image(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContent": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContent(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContentSourceDownload": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContentSourceDownload(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ImageFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageHash": schema_openstack_resource_controller_v2_api_v1alpha1_ImageHash(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageImport": schema_openstack_resource_controller_v2_api_v1alpha1_ImageImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageList": schema_openstack_resource_controller_v2_api_v1alpha1_ImageList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageProperties": schema_openstack_resource_controller_v2_api_v1alpha1_ImageProperties(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesHardware": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesHardware(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesOperatingSystem": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesOperatingSystem(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatusExtra": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatusExtra(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPair": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPair(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairFilter": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairImport": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairList": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions": schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Network": schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkImport": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkList": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NeutronStatusMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_NeutronStatusMetadata(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Port": schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortFilter": schema_openstack_resource_controller_v2_api_v1alpha1_PortFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortImport": schema_openstack_resource_controller_v2_api_v1alpha1_PortImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortList": schema_openstack_resource_controller_v2_api_v1alpha1_PortList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Project": schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectImport": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectList": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Router": schema_openstack_resource_controller_v2_api_v1alpha1_Router(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RouterFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterImport": schema_openstack_resource_controller_v2_api_v1alpha1_RouterImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterface": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterface(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroup": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroup(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRule": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRule(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRuleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRuleStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Server": schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroup": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRules": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRules(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRulesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRulesStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerImport(ref), - "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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Service": schema_openstack_resource_controller_v2_api_v1alpha1_Service(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceList": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Subnet": schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), - "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.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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadata(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadataStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeType": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeType(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpecStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpecStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeStatus(ref), - "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference": schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Domain": schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainFilter": schema_openstack_resource_controller_v2_api_v1alpha1_DomainFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainImport": schema_openstack_resource_controller_v2_api_v1alpha1_DomainImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainList": schema_openstack_resource_controller_v2_api_v1alpha1_DomainList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_DomainResourceSpec(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByNeutronTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByNeutronTags(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByServerTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByServerTags(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FixedIPStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Flavor": schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorList": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIP": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIP(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPImport": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPList": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Group": schema_openstack_resource_controller_v2_api_v1alpha1_Group(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_GroupFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_GroupImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupList": schema_openstack_resource_controller_v2_api_v1alpha1_GroupList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_GroupResourceSpec(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Image": schema_openstack_resource_controller_v2_api_v1alpha1_Image(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContent": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContent(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContentSourceDownload": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContentSourceDownload(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ImageFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageHash": schema_openstack_resource_controller_v2_api_v1alpha1_ImageHash(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageImport": schema_openstack_resource_controller_v2_api_v1alpha1_ImageImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageList": schema_openstack_resource_controller_v2_api_v1alpha1_ImageList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageProperties": schema_openstack_resource_controller_v2_api_v1alpha1_ImageProperties(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesHardware": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesHardware(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesOperatingSystem": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesOperatingSystem(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatusExtra": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatusExtra(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPair": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPair(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairFilter": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairImport": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairList": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions": schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Network": schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkImport": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkList": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NeutronStatusMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_NeutronStatusMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Port": schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortFilter": schema_openstack_resource_controller_v2_api_v1alpha1_PortFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortImport": schema_openstack_resource_controller_v2_api_v1alpha1_PortImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortList": schema_openstack_resource_controller_v2_api_v1alpha1_PortList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Project": schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectImport": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectList": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Router": schema_openstack_resource_controller_v2_api_v1alpha1_Router(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RouterFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterImport": schema_openstack_resource_controller_v2_api_v1alpha1_RouterImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterface": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterface(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroup": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroup(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRule": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRule(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRuleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRuleStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Server": schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroup": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRules": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRules(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRulesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRulesStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerImport(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Service": schema_openstack_resource_controller_v2_api_v1alpha1_Service(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceList": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Subnet": schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), + "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.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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadataStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeType": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeType(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpecStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpecStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeStatus(ref), + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), "k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref), "k8s.io/api/core/v1.AppArmorProfile": schema_k8sio_api_core_v1_AppArmorProfile(ref), "k8s.io/api/core/v1.AttachedVolume": schema_k8sio_api_core_v1_AttachedVolume(ref), @@ -1038,6 +1041,109 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStat } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialFilter 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: "", + }, + }, + "userRef": { + SchemaProps: spec.SchemaProps{ + Description: "userRef is a reference to the ORC User which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialResourceSpec 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: "", + }, + }, + "userRef": { + SchemaProps: spec.SchemaProps{ + Description: "userRef is a reference to the ORC User which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"userRef"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialResourceStatus 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: "", + }, + }, + "userID": { + SchemaProps: spec.SchemaProps{ + Description: "userID is the ID of the User to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index dbadb7068..3b67eb9fa 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -18,6 +18,7 @@ rules: - openstack.k-orc.cloud resources: - addressscopes + - applicationcredentials - domains - endpoints - flavors @@ -52,6 +53,7 @@ rules: - openstack.k-orc.cloud resources: - addressscopes/status + - applicationcredentials/status - domains/status - endpoints/status - flavors/status diff --git a/config/samples/openstack_v1alpha1_applicationcredential.yaml b/config/samples/openstack_v1alpha1_applicationcredential.yaml new file mode 100644 index 000000000..8edc2aba1 --- /dev/null +++ b/config/samples/openstack_v1alpha1_applicationcredential.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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 ApplicationCredential + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/applicationcredential/actuator.go b/internal/controllers/applicationcredential/actuator.go new file mode 100644 index 000000000..1b3ad3efc --- /dev/null +++ b/internal/controllers/applicationcredential/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 applicationcredential + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials" + 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 = applicationcredentials.ApplicationCredential + + 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 applicationcredentialActuator struct { + osClient osclients.ApplicationCredentialClient + k8sClient client.Client +} + +var _ createResourceActuator = applicationcredentialActuator{} +var _ deleteResourceActuator = applicationcredentialActuator{} + +func (applicationcredentialActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator applicationcredentialActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetApplicationCredential(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator applicationcredentialActuator) 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 := applicationcredentials.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListApplicationCredentials(ctx, listOpts), true +} + +func (actuator applicationcredentialActuator) 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 + + user, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.UserRef, "User", + func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := applicationcredentials.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + UserID: ptr.Deref(user.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListApplicationCredentials(ctx, listOpts), reconcileStatus +} + +func (actuator applicationcredentialActuator) 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 userID string + user, userDepRS := userDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS) + if user != nil { + userID = ptr.Deref(user.Status.ID, "") + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := applicationcredentials.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + UserID: userID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateApplicationCredential(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 applicationcredentialActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, resource.ID)) +} + +func (actuator applicationcredentialActuator) 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 := applicationcredentials.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.UpdateApplicationCredential(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 applicationcredentials.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToApplicationCredentialUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["application_credentials"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *applicationcredentials.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *applicationcredentials.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator applicationcredentialActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type applicationcredentialHelperFactory struct{} + +var _ helperFactory = applicationcredentialHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.ApplicationCredential, controller interfaces.ResourceController) (applicationcredentialActuator, 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 applicationcredentialActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return applicationcredentialActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewApplicationCredentialClient() + if err != nil { + return applicationcredentialActuator{}, progress.WrapError(err) + } + + return applicationcredentialActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (applicationcredentialHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return applicationcredentialAdapter{obj} +} + +func (applicationcredentialHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (applicationcredentialHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/applicationcredential/actuator_test.go b/internal/controllers/applicationcredential/actuator_test.go new file mode 100644 index 000000000..1a8015f89 --- /dev/null +++ b/internal/controllers/applicationcredential/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 applicationcredential + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials" + 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 applicationcredentials.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: applicationcredentials.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: applicationcredentials.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.ApplicationCredential{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.ApplicationCredentialSpec{ + Resource: &orcv1alpha1.ApplicationCredentialResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := applicationcredentials.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.ApplicationCredentialResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := applicationcredentials.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/applicationcredential/controller.go b/internal/controllers/applicationcredential/controller.go new file mode 100644 index 000000000..34a86a51e --- /dev/null +++ b/internal/controllers/applicationcredential/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 applicationcredential + +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 = "applicationcredential" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=applicationcredentials,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=applicationcredentials/status,verbs=get;update;patch + +type applicationcredentialReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return applicationcredentialReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (applicationcredentialReconcilerConstructor) GetName() string { + return controllerName +} + +var userDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ApplicationCredentialList, *orcv1alpha1.User]( + "spec.resource.userRef", + func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { + resource := applicationcredential.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.UserRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var userImportDependency = dependency.NewDependency[*orcv1alpha1.ApplicationCredentialList, *orcv1alpha1.User]( + "spec.import.filter.userRef", + func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { + resource := applicationcredential.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.UserRef == nil { + return nil + } + return []string{string(*resource.Filter.UserRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c applicationcredentialReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + userWatchEventHandler, err := userDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + userImportWatchEventHandler, err := userImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.User{}, userWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.User{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.User{}, userImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.User{})), + ). + For(&orcv1alpha1.ApplicationCredential{}) + + if err := errors.Join( + userDependency.AddToManager(ctx, mgr), + userImportDependency.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, applicationcredentialHelperFactory{}, applicationcredentialStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/applicationcredential/status.go b/internal/controllers/applicationcredential/status.go new file mode 100644 index 000000000..34141fd33 --- /dev/null +++ b/internal/controllers/applicationcredential/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 applicationcredential + +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 applicationcredentialStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.ApplicationCredentialApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.ApplicationCredentialStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.ApplicationCredential, *osResourceT, *objectApplyT, *statusApplyT] = applicationcredentialStatusWriter{} + +func (applicationcredentialStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.ApplicationCredential(name, namespace) +} + +func (applicationcredentialStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.ApplicationCredential, 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 (applicationcredentialStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.ApplicationCredentialResourceStatus(). + WithUserID(osResource.UserID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the ApplicationCredentialResourceStatus 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/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml new file mode 100644 index 000000000..709036a53 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-create-full +status: + resource: + name: applicationcredential-create-full-override + description: ApplicationCredential 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: ApplicationCredential + name: applicationcredential-create-full + ref: applicationcredential + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: applicationcredential-create-full + ref: user +assertAll: + - celExpr: "applicationcredential.status.id != ''" + - celExpr: "applicationcredential.status.resource.userID == user.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml new file mode 100644 index 000000000..b95030658 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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: applicationcredential-create-full-override + description: ApplicationCredential from "create full" test + userRef: applicationcredential-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-create-full/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/README.md new file mode 100644 index 000000000..77afac10d --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/README.md @@ -0,0 +1,11 @@ +# Create a ApplicationCredential with all the options + +## Step 00 + +Create a ApplicationCredential 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/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml new file mode 100644 index 000000000..0e6d4d81f --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-create-minimal +status: + resource: + name: applicationcredential-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: ApplicationCredential + name: applicationcredential-create-minimal + ref: applicationcredential + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: applicationcredential-create-minimal + ref: user +assertAll: + - celExpr: "applicationcredential.status.id != ''" + - celExpr: "applicationcredential.status.resource.userID == user.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..f62a6c7b3 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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: + userRef: applicationcredential-create-minimal diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-create-minimal/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-assert.yaml new file mode 100644 index 000000000..6362f0b10 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential' in secret.metadata.finalizers" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-create-minimal/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/README.md new file mode 100644 index 000000000..79cd913b3 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a ApplicationCredential with the minimum options + +## Step 00 + +Create a minimal ApplicationCredential, 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/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml new file mode 100644 index 000000000..a4c40956e --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/applicationcredential-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/applicationcredential-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-user +status: + conditions: + - type: Available + message: Waiting for User/applicationcredential-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for User/applicationcredential-dependency-pending to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..3fec6964c --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-user +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + userRef: applicationcredential-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource + +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: applicationcredential-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + userRef: applicationcredential-dependency diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml new file mode 100644 index 000000000..89e29a72d --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-user +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/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..75e318e17 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic applicationcredential-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-dependency/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/02-assert.yaml new file mode 100644 index 000000000..3c90253ef --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/02-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: applicationcredential-dependency + ref: user + - apiVersion: v1 + kind: Secret + name: applicationcredential-dependency + ref: secret +assertAll: + - celExpr: "user.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/applicationcredential' in user.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/applicationcredential' in secret.metadata.finalizers" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/02-delete-dependencies.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..8ff104281 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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 user.openstack.k-orc.cloud applicationcredential-dependency --wait=false + namespaced: true + - command: kubectl delete secret applicationcredential-dependency --wait=false + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-assert.yaml new file mode 100644 index 000000000..4d6be7f09 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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 user.openstack.k-orc.cloud applicationcredential-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret applicationcredential-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..bed278a33 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-dependency-no-user diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/README.md new file mode 100644 index 000000000..9d26de6a7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create ApplicationCredentials referencing non-existing resources. Each ApplicationCredential is dependent on other non-existing resource. Verify that the ApplicationCredentials are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the ApplicationCredentials 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 ApplicationCredentials and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-assert.yaml new file mode 100644 index 000000000..c9a2e3f75 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for User/applicationcredential-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for User/applicationcredential-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..8877b2e99 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: applicationcredential-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + userRef: applicationcredential-import-dependency diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-import-dependency/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-assert.yaml new file mode 100644 index 000000000..483ed5c58 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for User/applicationcredential-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for User/applicationcredential-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..d3fc3873a --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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 `applicationcredential-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: + userRef: applicationcredential-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml new file mode 100644 index 000000000..318452565 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-import-dependency + ref: applicationcredential1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-import-dependency-not-this-one + ref: applicationcredential2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: applicationcredential-import-dependency + ref: user +assertAll: + - celExpr: "applicationcredential1.status.id != applicationcredential2.status.id" + - celExpr: "applicationcredential1.status.resource.userID == user.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..2f9738f37 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + userRef: applicationcredential-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-assert.yaml new file mode 100644 index 000000000..d74971a73 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get user.openstack.k-orc.cloud applicationcredential-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..cc650c8c4 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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 user.openstack.k-orc.cloud applicationcredential-import-dependency + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-assert.yaml new file mode 100644 index 000000000..4639c46a8 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get applicationcredential.openstack.k-orc.cloud applicationcredential-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-delete-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..5a0b676bb --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-import-dependency diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/README.md new file mode 100644 index 000000000..2be49341d --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported ApplicationCredential + +## Step 00 + +Import a ApplicationCredential that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the ApplicationCredential is waiting for the dependency to be ready. + +## Step 01 + +Create a ApplicationCredential matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a ApplicationCredential matching the import filters. + +Verify that the observed status on the imported ApplicationCredential corresponds to the spec of the created ApplicationCredential. + +## 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 ApplicationCredential and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-assert.yaml new file mode 100644 index 000000000..00afbdf41 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml new file mode 100644 index 000000000..611b974e8 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential from "import error" test + userRef: applicationcredential-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential from "import error" test + userRef: applicationcredential-import-error + # TODO(scaffolding): add any required field diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-import-error/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-assert.yaml new file mode 100644 index 000000000..e97dd9774 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml new file mode 100644 index 000000000..e70ca24b4 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: ApplicationCredential from "import error" test diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/README.md new file mode 100644 index 000000000..acd794097 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/README.md @@ -0,0 +1,13 @@ +# Import ApplicationCredential with more than one matching resources + +## Step 00 + +Create two ApplicationCredentials with identical specs. + +## Step 01 + +Ensure that an imported ApplicationCredential with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-assert.yaml new file mode 100644 index 000000000..53ef7ac86 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml new file mode 100644 index 000000000..f000ecfd5 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: applicationcredential-import-external + description: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-import/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml new file mode 100644 index 000000000..3e0618259 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: applicationcredential-import-external-not-this-one + description: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..66edf6003 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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 `applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test + userRef: applicationcredential-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml new file mode 100644 index 000000000..7412f2324 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-import-external + ref: applicationcredential1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-import-external-not-this-one + ref: applicationcredential2 +assertAll: + - celExpr: "applicationcredential1.status.id != applicationcredential2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-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: applicationcredential-import-external + description: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml new file mode 100644 index 000000000..c82fab8b5 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test + userRef: applicationcredential-import + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-import/README.md new file mode 100644 index 000000000..36422d1a4 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/README.md @@ -0,0 +1,18 @@ +# Import ApplicationCredential + +## Step 00 + +Import a applicationcredential that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a applicationcredential 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 applicationcredential matching the filter and verify that the observed status on the imported applicationcredential corresponds to the spec of the created applicationcredential. +Also, confirm that it does not adopt any applicationcredential whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml new file mode 100644 index 000000000..fc2cfc9b5 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-update + ref: applicationcredential +assertAll: + - celExpr: "!has(applicationcredential.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-update +status: + resource: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml new file mode 100644 index 000000000..b391c1818 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-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: ApplicationCredential +metadata: + name: applicationcredential-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: + userRef: applicationcredential-update diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-update/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/01-assert.yaml new file mode 100644 index 000000000..bb022f746 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-update +status: + resource: + name: applicationcredential-update-updated + description: applicationcredential-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/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml new file mode 100644 index 000000000..ad1de371a --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-update +spec: + resource: + name: applicationcredential-update-updated + description: applicationcredential-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml new file mode 100644 index 000000000..ec6e12d54 --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-update + ref: applicationcredential +assertAll: + - celExpr: "!has(applicationcredential.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-update +status: + resource: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-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/applicationcredential/tests/applicationcredential-update/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-update/README.md new file mode 100644 index 000000000..020723c0e --- /dev/null +++ b/internal/controllers/applicationcredential/tests/applicationcredential-update/README.md @@ -0,0 +1,17 @@ +# Update ApplicationCredential + +## Step 00 + +Create a ApplicationCredential 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/applicationcredential.go b/internal/osclients/applicationcredential.go new file mode 100644 index 000000000..1dcda69a9 --- /dev/null +++ b/internal/osclients/applicationcredential.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/applicationcredentials" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type ApplicationCredentialClient interface { + ListApplicationCredentials(ctx context.Context, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] + CreateApplicationCredential(ctx context.Context, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) + DeleteApplicationCredential(ctx context.Context, resourceID string) error + GetApplicationCredential(ctx context.Context, resourceID string) (*applicationcredentials.ApplicationCredential, error) + UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) +} + +type applicationcredentialClient struct{ client *gophercloud.ServiceClient } + +// NewApplicationCredentialClient returns a new OpenStack client. +func NewApplicationCredentialClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (ApplicationCredentialClient, 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 applicationcredential service client: %v", err) + } + + return &applicationcredentialClient{client}, nil +} + +func (c applicationcredentialClient) ListApplicationCredentials(ctx context.Context, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { + pager := applicationcredentials.List(c.client, listOpts) + return func(yield func(*applicationcredentials.ApplicationCredential, error) bool) { + _ = pager.EachPage(ctx, yieldPage(applicationcredentials.ExtractApplicationCredentials, yield)) + } +} + +func (c applicationcredentialClient) CreateApplicationCredential(ctx context.Context, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + return applicationcredentials.Create(ctx, c.client, opts).Extract() +} + +func (c applicationcredentialClient) DeleteApplicationCredential(ctx context.Context, resourceID string) error { + return applicationcredentials.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c applicationcredentialClient) GetApplicationCredential(ctx context.Context, resourceID string) (*applicationcredentials.ApplicationCredential, error) { + return applicationcredentials.Get(ctx, c.client, resourceID).Extract() +} + +func (c applicationcredentialClient) UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + return applicationcredentials.Update(ctx, c.client, id, opts).Extract() +} + +type applicationcredentialErrorClient struct{ error } + +// NewApplicationCredentialErrorClient returns a ApplicationCredentialClient in which every method returns the given error. +func NewApplicationCredentialErrorClient(e error) ApplicationCredentialClient { + return applicationcredentialErrorClient{e} +} + +func (e applicationcredentialErrorClient) ListApplicationCredentials(_ context.Context, _ applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { + return func(yield func(*applicationcredentials.ApplicationCredential, error) bool) { + yield(nil, e.error) + } +} + +func (e applicationcredentialErrorClient) CreateApplicationCredential(_ context.Context, _ applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + return nil, e.error +} + +func (e applicationcredentialErrorClient) DeleteApplicationCredential(_ context.Context, _ string) error { + return e.error +} + +func (e applicationcredentialErrorClient) GetApplicationCredential(_ context.Context, _ string) (*applicationcredentials.ApplicationCredential, error) { + return nil, e.error +} + +func (e applicationcredentialErrorClient) UpdateApplicationCredential(_ context.Context, _ string, _ applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + return nil, e.error +} diff --git a/test/apivalidations/applicationcredential_test.go b/test/apivalidations/applicationcredential_test.go new file mode 100644 index 000000000..615fb4408 --- /dev/null +++ b/test/apivalidations/applicationcredential_test.go @@ -0,0 +1,128 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 ( + applicationcredentialName = "applicationcredential" + applicationcredentialID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func applicationcredentialStub(namespace *corev1.Namespace) *orcv1alpha1.ApplicationCredential { + obj := &orcv1alpha1.ApplicationCredential{} + obj.Name = applicationcredentialName + obj.Namespace = namespace.Name + return obj +} + +func testApplicationCredentialResource() *applyconfigv1alpha1.ApplicationCredentialResourceSpecApplyConfiguration { + return applyconfigv1alpha1.ApplicationCredentialResourceSpec(). + WithUserRef("user") +} + +func baseApplicationCredentialPatch(obj client.Object) *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration { + return applyconfigv1alpha1.ApplicationCredential(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.ApplicationCredentialSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testApplicationCredentialImport() *applyconfigv1alpha1.ApplicationCredentialImportApplyConfiguration { + return applyconfigv1alpha1.ApplicationCredentialImport().WithID(applicationcredentialID) +} + +var _ = Describe("ORC ApplicationCredential API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.ApplicationCredentialApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return applicationcredentialStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration { + return baseApplicationCredentialPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithResource(testApplicationCredentialResource()) + }, + applyImport: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithImport(testApplicationCredentialImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ApplicationCredentialImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ApplicationCredentialImport().WithFilter(applyconfigv1alpha1.ApplicationCredentialFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.ApplicationCredentialImport().WithFilter(applyconfigv1alpha1.ApplicationCredentialFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.ApplicationCredential).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.ApplicationCredential).Spec.ManagedOptions.OnDelete + }, + }) + + It("should reject a applicationcredential without required fields", func(ctx context.Context) { + obj := applicationcredentialStub(namespace) + patch := baseApplicationCredentialPatch(obj) + patch.Spec.WithResource(applyconfigv1alpha1.ApplicationCredentialResourceSpec()) + Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) + }) + + It("should have immutable userRef", func(ctx context.Context) { + obj := applicationcredentialStub(namespace) + patch := baseApplicationCredentialPatch(obj) + patch.Spec.WithResource(testApplicationCredentialResource(). + WithUserRef("user-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testApplicationCredentialResource(). + WithUserRef("user-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("userRef is immutable"))) + }) + + // TODO(scaffolding): Add more resource-specific validation tests. + // Some common things to test: + // - Immutability of fields with `self == oldSelf` validation + // - Enum validation (valid and invalid values) + // - Numeric range validation (min/max bounds) + // - Tag uniqueness (if the resource has tags with listType=set) + // - Format validation (CIDR, UUID, etc.) + // - Cross-field validation rules +}) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index aee5bb6d7..0d21d8ba6 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -256,6 +256,12 @@ _Appears in:_ | `mac` _string_ | mac contains a MAC address which a server connected to the port can
send packets with. | | MaxLength: 1024
Optional: \{\}
| + + + + + + #### AvailabilityZoneHint _Underlying type:_ _string_ @@ -1915,6 +1921,8 @@ _Appears in:_ - [Address](#address) - [AddressScopeFilter](#addressscopefilter) - [AddressScopeResourceSpec](#addressscoperesourcespec) +- [ApplicationCredentialFilter](#applicationcredentialfilter) +- [ApplicationCredentialResourceSpec](#applicationcredentialresourcespec) - [EndpointFilter](#endpointfilter) - [EndpointResourceSpec](#endpointresourcespec) - [ExternalGateway](#externalgateway) @@ -2330,6 +2338,8 @@ _Validation:_ _Appears in:_ - [AddressScopeFilter](#addressscopefilter) - [AddressScopeResourceSpec](#addressscoperesourcespec) +- [ApplicationCredentialFilter](#applicationcredentialfilter) +- [ApplicationCredentialResourceSpec](#applicationcredentialresourcespec) - [FlavorFilter](#flavorfilter) - [FlavorResourceSpec](#flavorresourcespec) - [ImageFilter](#imagefilter) From a2a73e9f8684f2092d3daf200506c84015d9f590 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Tue, 10 Mar 2026 16:09:23 +0100 Subject: [PATCH 128/237] Scaffolding for the ApplicationCredential controller Register with the resource generator On-behalf-of: SAP nils.gondermann@sap.com --- PROJECT | 8 + ...enerated.applicationcredential-resource.go | 179 ++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 147 +++++++++ cmd/models-schema/zz_generated.openapi.go | 235 +++++++++++++ cmd/resource-generator/main.go | 3 + ...ck.k-orc.cloud_applicationcredentials.yaml | 311 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + config/samples/kustomization.yaml | 1 + .../zz_generated.adapter.go | 88 +++++ .../zz_generated.controller.go | 45 +++ .../osclients/mock/applicationcredential.go | 131 ++++++++ internal/osclients/mock/doc.go | 3 + kuttl-test.yaml | 1 + .../api/v1alpha1/applicationcredential.go | 281 ++++++++++++++++ .../v1alpha1/applicationcredentialfilter.go | 61 ++++ .../v1alpha1/applicationcredentialimport.go | 48 +++ .../applicationcredentialresourcespec.go | 61 ++++ .../applicationcredentialresourcestatus.go | 57 ++++ .../api/v1alpha1/applicationcredentialspec.go | 79 +++++ .../v1alpha1/applicationcredentialstatus.go | 66 ++++ .../applyconfiguration/internal/internal.go | 102 ++++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/applicationcredential.go | 74 +++++ .../api/v1alpha1/fake/fake_api_client.go | 4 + .../fake/fake_applicationcredential.go | 53 +++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../api/v1alpha1/applicationcredential.go | 102 ++++++ .../api/v1alpha1/interface.go | 7 + .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/applicationcredential.go | 70 ++++ .../api/v1alpha1/expansion_generated.go | 8 + website/docs/crd-reference.md | 130 ++++++++ 33 files changed, 2379 insertions(+) create mode 100644 api/v1alpha1/zz_generated.applicationcredential-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml create mode 100644 internal/controllers/applicationcredential/zz_generated.adapter.go create mode 100644 internal/controllers/applicationcredential/zz_generated.controller.go create mode 100644 internal/osclients/mock/applicationcredential.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredential.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/applicationcredential.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_applicationcredential.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/applicationcredential.go create mode 100644 pkg/clients/listers/api/v1alpha1/applicationcredential.go diff --git a/PROJECT b/PROJECT index b3181e74c..73daa42d0 100644 --- a/PROJECT +++ b/PROJECT @@ -16,6 +16,14 @@ resources: kind: AddressScope path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: ApplicationCredential + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/zz_generated.applicationcredential-resource.go b/api/v1alpha1/zz_generated.applicationcredential-resource.go new file mode 100644 index 000000000..d949c84d0 --- /dev/null +++ b/api/v1alpha1/zz_generated.applicationcredential-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" +) + +// ApplicationCredentialImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type ApplicationCredentialImport 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 *ApplicationCredentialFilter `json:"filter,omitempty"` +} + +// ApplicationCredentialSpec 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 ApplicationCredentialSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *ApplicationCredentialImport `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 *ApplicationCredentialResourceSpec `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"` +} + +// ApplicationCredentialStatus defines the observed state of an ORC resource. +type ApplicationCredentialStatus 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 *ApplicationCredentialResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &ApplicationCredential{} + +func (i *ApplicationCredential) 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" + +// ApplicationCredential is the Schema for an ORC resource. +type ApplicationCredential 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 ApplicationCredentialSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status ApplicationCredentialStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ApplicationCredentialList contains a list of ApplicationCredential. +type ApplicationCredentialList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of ApplicationCredential. + // +required + Items []ApplicationCredential `json:"items"` +} + +func (l *ApplicationCredentialList) GetItems() []ApplicationCredential { + return l.Items +} + +func init() { + SchemeBuilder.Register(&ApplicationCredential{}, &ApplicationCredentialList{}) +} + +func (i *ApplicationCredential) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &ApplicationCredential{} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3d16b852d..9ad226cf9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -337,6 +337,33 @@ func (in *AllowedAddressPairStatus) DeepCopy() *AllowedAddressPairStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredential) DeepCopyInto(out *ApplicationCredential) { + *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 ApplicationCredential. +func (in *ApplicationCredential) DeepCopy() *ApplicationCredential { + if in == nil { + return nil + } + out := new(ApplicationCredential) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApplicationCredential) 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 *ApplicationCredentialFilter) DeepCopyInto(out *ApplicationCredentialFilter) { *out = *in @@ -367,6 +394,63 @@ func (in *ApplicationCredentialFilter) DeepCopy() *ApplicationCredentialFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialImport) DeepCopyInto(out *ApplicationCredentialImport) { + *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(ApplicationCredentialFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialImport. +func (in *ApplicationCredentialImport) DeepCopy() *ApplicationCredentialImport { + if in == nil { + return nil + } + out := new(ApplicationCredentialImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialList) DeepCopyInto(out *ApplicationCredentialList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApplicationCredential, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialList. +func (in *ApplicationCredentialList) DeepCopy() *ApplicationCredentialList { + if in == nil { + return nil + } + out := new(ApplicationCredentialList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApplicationCredentialList) 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 *ApplicationCredentialResourceSpec) DeepCopyInto(out *ApplicationCredentialResourceSpec) { *out = *in @@ -407,6 +491,69 @@ func (in *ApplicationCredentialResourceStatus) DeepCopy() *ApplicationCredential return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialSpec) DeepCopyInto(out *ApplicationCredentialSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(ApplicationCredentialImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(ApplicationCredentialResourceSpec) + (*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 ApplicationCredentialSpec. +func (in *ApplicationCredentialSpec) DeepCopy() *ApplicationCredentialSpec { + if in == nil { + return nil + } + out := new(ApplicationCredentialSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialStatus) DeepCopyInto(out *ApplicationCredentialStatus) { + *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(ApplicationCredentialResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialStatus. +func (in *ApplicationCredentialStatus) DeepCopy() *ApplicationCredentialStatus { + if in == nil { + return nil + } + out := new(ApplicationCredentialStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudCredentialsReference) DeepCopyInto(out *CloudCredentialsReference) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 15a4875ed..637b3a0d8 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -43,9 +43,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "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), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredential": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredential(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialImport": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialList": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialList(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference": schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Domain": schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainFilter": schema_openstack_resource_controller_v2_api_v1alpha1_DomainFilter(ref), @@ -1041,6 +1046,57 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStat } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredential(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredential 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.ApplicationCredentialSpec"), + }, + }, + "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.ApplicationCredentialStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1075,6 +1131,85 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialF } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialImport 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.ApplicationCredentialFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialList contains a list of ApplicationCredential.", + 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 ApplicationCredential.", + 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.ApplicationCredential"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredential", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1144,6 +1279,106 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialR } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialSpec 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.ApplicationCredentialImport"), + }, + }, + "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.ApplicationCredentialResourceSpec"), + }, + }, + "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.ApplicationCredentialImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec", "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_ApplicationCredentialStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialStatus 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.ApplicationCredentialResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(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 37c577523..2ff83df6e 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -174,6 +174,9 @@ var resources []templateFields = []templateFields{ { Name: "AddressScope", }, + { + Name: "ApplicationCredential", + }, } // These resources won't be generated diff --git a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml new file mode 100644 index 000000000..449790ab8 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml @@ -0,0 +1,311 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: applicationcredentials.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: ApplicationCredential + listKind: ApplicationCredentialList + plural: applicationcredentials + singular: applicationcredential + 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: ApplicationCredential is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + name: + description: name of the existing resource + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + userRef: + description: userRef is a reference to the ORC User which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + maxLength: 36 + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + 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 + userRef: + description: userRef is a reference to the ORC User which this + resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: userRef is immutable + rule: self == oldSelf + required: + - userRef + 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: + 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 + userID: + description: userID is the ID of the User to which the resource + is associated. + maxLength: 1024 + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 592ed3b14..85a318b42 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_addressscopes.yaml +- bases/openstack.k-orc.cloud_applicationcredentials.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 eb819d448..4b86755db 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -2,6 +2,7 @@ ## Append samples of your project ## resources: - openstack_v1alpha1_addressscope.yaml +- openstack_v1alpha1_applicationcredential.yaml - openstack_v1alpha1_domain.yaml - openstack_v1alpha1_endpoint.yaml - openstack_v1alpha1_flavor.yaml diff --git a/internal/controllers/applicationcredential/zz_generated.adapter.go b/internal/controllers/applicationcredential/zz_generated.adapter.go new file mode 100644 index 000000000..55f0b5346 --- /dev/null +++ b/internal/controllers/applicationcredential/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 applicationcredential + +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.ApplicationCredential + orcObjectListT = orcv1alpha1.ApplicationCredentialList + resourceSpecT = orcv1alpha1.ApplicationCredentialResourceSpec + filterT = orcv1alpha1.ApplicationCredentialFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = applicationcredentialAdapter +) + +type applicationcredentialAdapter struct { + *orcv1alpha1.ApplicationCredential +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.ApplicationCredential +} + +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/applicationcredential/zz_generated.controller.go b/internal/controllers/applicationcredential/zz_generated.controller.go new file mode 100644 index 000000000..09ae74111 --- /dev/null +++ b/internal/controllers/applicationcredential/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 applicationcredential + +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/applicationcredential.go b/internal/osclients/mock/applicationcredential.go new file mode 100644 index 000000000..194a49a2f --- /dev/null +++ b/internal/osclients/mock/applicationcredential.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: ../applicationcredential.go +// +// Generated by this command: +// +// mockgen -package mock -destination=applicationcredential.go -source=../applicationcredential.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ApplicationCredentialClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + applicationcredentials "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials" + gomock "go.uber.org/mock/gomock" +) + +// MockApplicationCredentialClient is a mock of ApplicationCredentialClient interface. +type MockApplicationCredentialClient struct { + ctrl *gomock.Controller + recorder *MockApplicationCredentialClientMockRecorder + isgomock struct{} +} + +// MockApplicationCredentialClientMockRecorder is the mock recorder for MockApplicationCredentialClient. +type MockApplicationCredentialClientMockRecorder struct { + mock *MockApplicationCredentialClient +} + +// NewMockApplicationCredentialClient creates a new mock instance. +func NewMockApplicationCredentialClient(ctrl *gomock.Controller) *MockApplicationCredentialClient { + mock := &MockApplicationCredentialClient{ctrl: ctrl} + mock.recorder = &MockApplicationCredentialClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockApplicationCredentialClient) EXPECT() *MockApplicationCredentialClientMockRecorder { + return m.recorder +} + +// CreateApplicationCredential mocks base method. +func (m *MockApplicationCredentialClient) CreateApplicationCredential(ctx context.Context, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateApplicationCredential", ctx, opts) + ret0, _ := ret[0].(*applicationcredentials.ApplicationCredential) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateApplicationCredential indicates an expected call of CreateApplicationCredential. +func (mr *MockApplicationCredentialClientMockRecorder) CreateApplicationCredential(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).CreateApplicationCredential), ctx, opts) +} + +// DeleteApplicationCredential mocks base method. +func (m *MockApplicationCredentialClient) DeleteApplicationCredential(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteApplicationCredential", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteApplicationCredential indicates an expected call of DeleteApplicationCredential. +func (mr *MockApplicationCredentialClientMockRecorder) DeleteApplicationCredential(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).DeleteApplicationCredential), ctx, resourceID) +} + +// GetApplicationCredential mocks base method. +func (m *MockApplicationCredentialClient) GetApplicationCredential(ctx context.Context, resourceID string) (*applicationcredentials.ApplicationCredential, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetApplicationCredential", ctx, resourceID) + ret0, _ := ret[0].(*applicationcredentials.ApplicationCredential) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetApplicationCredential indicates an expected call of GetApplicationCredential. +func (mr *MockApplicationCredentialClientMockRecorder) GetApplicationCredential(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).GetApplicationCredential), ctx, resourceID) +} + +// ListApplicationCredentials mocks base method. +func (m *MockApplicationCredentialClient) ListApplicationCredentials(ctx context.Context, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListApplicationCredentials", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*applicationcredentials.ApplicationCredential, error]) + return ret0 +} + +// ListApplicationCredentials indicates an expected call of ListApplicationCredentials. +func (mr *MockApplicationCredentialClientMockRecorder) ListApplicationCredentials(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListApplicationCredentials", reflect.TypeOf((*MockApplicationCredentialClient)(nil).ListApplicationCredentials), ctx, listOpts) +} + +// UpdateApplicationCredential mocks base method. +func (m *MockApplicationCredentialClient) UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateApplicationCredential", ctx, id, opts) + ret0, _ := ret[0].(*applicationcredentials.ApplicationCredential) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateApplicationCredential indicates an expected call of UpdateApplicationCredential. +func (mr *MockApplicationCredentialClientMockRecorder) UpdateApplicationCredential(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).UpdateApplicationCredential), ctx, id, opts) +} diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 71870a3a7..766500c8f 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -38,6 +38,9 @@ import ( //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=applicationcredential.go -source=../applicationcredential.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ApplicationCredentialClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt applicationcredential.go > _applicationcredential.go && mv _applicationcredential.go applicationcredential.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/kuttl-test.yaml b/kuttl-test.yaml index a6384f50d..d58526493 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -3,6 +3,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: - ./internal/controllers/addressscope/tests/ +- ./internal/controllers/applicationcredential/tests/ - ./internal/controllers/domain/tests/ - ./internal/controllers/endpoint/tests/ - ./internal/controllers/flavor/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredential.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredential.go new file mode 100644 index 000000000..db5eb36e8 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredential.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" +) + +// ApplicationCredentialApplyConfiguration represents a declarative configuration of the ApplicationCredential type for use +// with apply. +type ApplicationCredentialApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ApplicationCredentialSpecApplyConfiguration `json:"spec,omitempty"` + Status *ApplicationCredentialStatusApplyConfiguration `json:"status,omitempty"` +} + +// ApplicationCredential constructs a declarative configuration of the ApplicationCredential type for use with +// apply. +func ApplicationCredential(name, namespace string) *ApplicationCredentialApplyConfiguration { + b := &ApplicationCredentialApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("ApplicationCredential") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractApplicationCredential extracts the applied configuration owned by fieldManager from +// applicationCredential. If no managedFields are found in applicationCredential for fieldManager, a +// ApplicationCredentialApplyConfiguration 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. +// applicationCredential must be a unmodified ApplicationCredential API object that was retrieved from the Kubernetes API. +// ExtractApplicationCredential 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 ExtractApplicationCredential(applicationCredential *apiv1alpha1.ApplicationCredential, fieldManager string) (*ApplicationCredentialApplyConfiguration, error) { + return extractApplicationCredential(applicationCredential, fieldManager, "") +} + +// ExtractApplicationCredentialStatus is the same as ExtractApplicationCredential except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractApplicationCredentialStatus(applicationCredential *apiv1alpha1.ApplicationCredential, fieldManager string) (*ApplicationCredentialApplyConfiguration, error) { + return extractApplicationCredential(applicationCredential, fieldManager, "status") +} + +func extractApplicationCredential(applicationCredential *apiv1alpha1.ApplicationCredential, fieldManager string, subresource string) (*ApplicationCredentialApplyConfiguration, error) { + b := &ApplicationCredentialApplyConfiguration{} + err := managedfields.ExtractInto(applicationCredential, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredential"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(applicationCredential.Name) + b.WithNamespace(applicationCredential.Namespace) + + b.WithKind("ApplicationCredential") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b ApplicationCredentialApplyConfiguration) 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 *ApplicationCredentialApplyConfiguration) WithKind(value string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithAPIVersion(value string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithName(value string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithGenerateName(value string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithNamespace(value string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithUID(value types.UID) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithResourceVersion(value string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithGeneration(value int64) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithLabels(entries map[string]string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithAnnotations(entries map[string]string) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithFinalizers(values ...string) *ApplicationCredentialApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ApplicationCredentialApplyConfiguration) 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 *ApplicationCredentialApplyConfiguration) WithSpec(value *ApplicationCredentialSpecApplyConfiguration) *ApplicationCredentialApplyConfiguration { + 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 *ApplicationCredentialApplyConfiguration) WithStatus(value *ApplicationCredentialStatusApplyConfiguration) *ApplicationCredentialApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ApplicationCredentialApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ApplicationCredentialApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ApplicationCredentialApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ApplicationCredentialApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go new file mode 100644 index 000000000..6a7b1e7be --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.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" +) + +// ApplicationCredentialFilterApplyConfiguration represents a declarative configuration of the ApplicationCredentialFilter type for use +// with apply. +type ApplicationCredentialFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` +} + +// ApplicationCredentialFilterApplyConfiguration constructs a declarative configuration of the ApplicationCredentialFilter type for use with +// apply. +func ApplicationCredentialFilter() *ApplicationCredentialFilterApplyConfiguration { + return &ApplicationCredentialFilterApplyConfiguration{} +} + +// 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 *ApplicationCredentialFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *ApplicationCredentialFilterApplyConfiguration { + 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 *ApplicationCredentialFilterApplyConfiguration) WithDescription(value string) *ApplicationCredentialFilterApplyConfiguration { + b.Description = &value + return b +} + +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *ApplicationCredentialFilterApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *ApplicationCredentialFilterApplyConfiguration { + b.UserRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialimport.go new file mode 100644 index 000000000..b84df0314 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialimport.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 + +// ApplicationCredentialImportApplyConfiguration represents a declarative configuration of the ApplicationCredentialImport type for use +// with apply. +type ApplicationCredentialImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *ApplicationCredentialFilterApplyConfiguration `json:"filter,omitempty"` +} + +// ApplicationCredentialImportApplyConfiguration constructs a declarative configuration of the ApplicationCredentialImport type for use with +// apply. +func ApplicationCredentialImport() *ApplicationCredentialImportApplyConfiguration { + return &ApplicationCredentialImportApplyConfiguration{} +} + +// 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 *ApplicationCredentialImportApplyConfiguration) WithID(value string) *ApplicationCredentialImportApplyConfiguration { + 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 *ApplicationCredentialImportApplyConfiguration) WithFilter(value *ApplicationCredentialFilterApplyConfiguration) *ApplicationCredentialImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go new file mode 100644 index 000000000..f070d2766 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.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" +) + +// ApplicationCredentialResourceSpecApplyConfiguration represents a declarative configuration of the ApplicationCredentialResourceSpec type for use +// with apply. +type ApplicationCredentialResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` +} + +// ApplicationCredentialResourceSpecApplyConfiguration constructs a declarative configuration of the ApplicationCredentialResourceSpec type for use with +// apply. +func ApplicationCredentialResourceSpec() *ApplicationCredentialResourceSpecApplyConfiguration { + return &ApplicationCredentialResourceSpecApplyConfiguration{} +} + +// 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 *ApplicationCredentialResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *ApplicationCredentialResourceSpecApplyConfiguration { + 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 *ApplicationCredentialResourceSpecApplyConfiguration) WithDescription(value string) *ApplicationCredentialResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *ApplicationCredentialResourceSpecApplyConfiguration { + b.UserRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go new file mode 100644 index 000000000..ce52d9d53 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.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 + +// ApplicationCredentialResourceStatusApplyConfiguration represents a declarative configuration of the ApplicationCredentialResourceStatus type for use +// with apply. +type ApplicationCredentialResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + UserID *string `json:"userID,omitempty"` +} + +// ApplicationCredentialResourceStatusApplyConfiguration constructs a declarative configuration of the ApplicationCredentialResourceStatus type for use with +// apply. +func ApplicationCredentialResourceStatus() *ApplicationCredentialResourceStatusApplyConfiguration { + return &ApplicationCredentialResourceStatusApplyConfiguration{} +} + +// 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 *ApplicationCredentialResourceStatusApplyConfiguration) WithName(value string) *ApplicationCredentialResourceStatusApplyConfiguration { + 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 *ApplicationCredentialResourceStatusApplyConfiguration) WithDescription(value string) *ApplicationCredentialResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithUserID sets the UserID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserID field is set to the value of the last call. +func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithUserID(value string) *ApplicationCredentialResourceStatusApplyConfiguration { + b.UserID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go new file mode 100644 index 000000000..d73e0886b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.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" +) + +// ApplicationCredentialSpecApplyConfiguration represents a declarative configuration of the ApplicationCredentialSpec type for use +// with apply. +type ApplicationCredentialSpecApplyConfiguration struct { + Import *ApplicationCredentialImportApplyConfiguration `json:"import,omitempty"` + Resource *ApplicationCredentialResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// ApplicationCredentialSpecApplyConfiguration constructs a declarative configuration of the ApplicationCredentialSpec type for use with +// apply. +func ApplicationCredentialSpec() *ApplicationCredentialSpecApplyConfiguration { + return &ApplicationCredentialSpecApplyConfiguration{} +} + +// 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 *ApplicationCredentialSpecApplyConfiguration) WithImport(value *ApplicationCredentialImportApplyConfiguration) *ApplicationCredentialSpecApplyConfiguration { + 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 *ApplicationCredentialSpecApplyConfiguration) WithResource(value *ApplicationCredentialResourceSpecApplyConfiguration) *ApplicationCredentialSpecApplyConfiguration { + 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 *ApplicationCredentialSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *ApplicationCredentialSpecApplyConfiguration { + 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 *ApplicationCredentialSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *ApplicationCredentialSpecApplyConfiguration { + 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 *ApplicationCredentialSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *ApplicationCredentialSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go new file mode 100644 index 000000000..2dc54e77a --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.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" +) + +// ApplicationCredentialStatusApplyConfiguration represents a declarative configuration of the ApplicationCredentialStatus type for use +// with apply. +type ApplicationCredentialStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ApplicationCredentialResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// ApplicationCredentialStatusApplyConfiguration constructs a declarative configuration of the ApplicationCredentialStatus type for use with +// apply. +func ApplicationCredentialStatus() *ApplicationCredentialStatusApplyConfiguration { + return &ApplicationCredentialStatusApplyConfiguration{} +} + +// 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 *ApplicationCredentialStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *ApplicationCredentialStatusApplyConfiguration { + 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 *ApplicationCredentialStatusApplyConfiguration) WithID(value string) *ApplicationCredentialStatusApplyConfiguration { + 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 *ApplicationCredentialStatusApplyConfiguration) WithResource(value *ApplicationCredentialResourceStatusApplyConfiguration) *ApplicationCredentialStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 84688dc8a..2a454d065 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -196,6 +196,108 @@ var schemaYAML = typed.YAMLObject(`types: - name: mac type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredential + 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.ApplicationCredentialSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialFilter + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: userRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialResourceSpec + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: userRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialResourceStatus + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: userID + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialSpec + 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.ApplicationCredentialImport + - 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.ApplicationCredentialResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialStatus + 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.ApplicationCredentialResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index bb76ee624..373bc2636 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -56,6 +56,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.AllowedAddressPairApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("AllowedAddressPairStatus"): return &apiv1alpha1.AllowedAddressPairStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredential"): + return &apiv1alpha1.ApplicationCredentialApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialFilter"): + return &apiv1alpha1.ApplicationCredentialFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialImport"): + return &apiv1alpha1.ApplicationCredentialImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialResourceSpec"): + return &apiv1alpha1.ApplicationCredentialResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialResourceStatus"): + return &apiv1alpha1.ApplicationCredentialResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialSpec"): + return &apiv1alpha1.ApplicationCredentialSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialStatus"): + return &apiv1alpha1.ApplicationCredentialStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("CloudCredentialsReference"): return &apiv1alpha1.CloudCredentialsReferenceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Domain"): 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 78e698815..a145cab6f 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 AddressScopesGetter + ApplicationCredentialsGetter DomainsGetter EndpointsGetter FlavorsGetter @@ -62,6 +63,10 @@ func (c *OpenstackV1alpha1Client) AddressScopes(namespace string) AddressScopeIn return newAddressScopes(c, namespace) } +func (c *OpenstackV1alpha1Client) ApplicationCredentials(namespace string) ApplicationCredentialInterface { + return newApplicationCredentials(c, namespace) +} + func (c *OpenstackV1alpha1Client) Domains(namespace string) DomainInterface { return newDomains(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/applicationcredential.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/applicationcredential.go new file mode 100644 index 000000000..f3f3411c5 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/applicationcredential.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" +) + +// ApplicationCredentialsGetter has a method to return a ApplicationCredentialInterface. +// A group's client should implement this interface. +type ApplicationCredentialsGetter interface { + ApplicationCredentials(namespace string) ApplicationCredentialInterface +} + +// ApplicationCredentialInterface has methods to work with ApplicationCredential resources. +type ApplicationCredentialInterface interface { + Create(ctx context.Context, applicationCredential *apiv1alpha1.ApplicationCredential, opts v1.CreateOptions) (*apiv1alpha1.ApplicationCredential, error) + Update(ctx context.Context, applicationCredential *apiv1alpha1.ApplicationCredential, opts v1.UpdateOptions) (*apiv1alpha1.ApplicationCredential, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, applicationCredential *apiv1alpha1.ApplicationCredential, opts v1.UpdateOptions) (*apiv1alpha1.ApplicationCredential, 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.ApplicationCredential, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.ApplicationCredentialList, 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.ApplicationCredential, err error) + Apply(ctx context.Context, applicationCredential *applyconfigurationapiv1alpha1.ApplicationCredentialApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.ApplicationCredential, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, applicationCredential *applyconfigurationapiv1alpha1.ApplicationCredentialApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.ApplicationCredential, err error) + ApplicationCredentialExpansion +} + +// applicationCredentials implements ApplicationCredentialInterface +type applicationCredentials struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.ApplicationCredential, *apiv1alpha1.ApplicationCredentialList, *applyconfigurationapiv1alpha1.ApplicationCredentialApplyConfiguration] +} + +// newApplicationCredentials returns a ApplicationCredentials +func newApplicationCredentials(c *OpenstackV1alpha1Client, namespace string) *applicationCredentials { + return &applicationCredentials{ + gentype.NewClientWithListAndApply[*apiv1alpha1.ApplicationCredential, *apiv1alpha1.ApplicationCredentialList, *applyconfigurationapiv1alpha1.ApplicationCredentialApplyConfiguration]( + "applicationcredentials", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.ApplicationCredential { return &apiv1alpha1.ApplicationCredential{} }, + func() *apiv1alpha1.ApplicationCredentialList { return &apiv1alpha1.ApplicationCredentialList{} }, + ), + } +} 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 4f4334818..87bc2b39d 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) AddressScopes(namespace string) v1alpha1.Address return newFakeAddressScopes(c, namespace) } +func (c *FakeOpenstackV1alpha1) ApplicationCredentials(namespace string) v1alpha1.ApplicationCredentialInterface { + return newFakeApplicationCredentials(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Domains(namespace string) v1alpha1.DomainInterface { return newFakeDomains(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_applicationcredential.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_applicationcredential.go new file mode 100644 index 000000000..c093e7c28 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_applicationcredential.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" +) + +// fakeApplicationCredentials implements ApplicationCredentialInterface +type fakeApplicationCredentials struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.ApplicationCredential, *v1alpha1.ApplicationCredentialList, *apiv1alpha1.ApplicationCredentialApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeApplicationCredentials(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.ApplicationCredentialInterface { + return &fakeApplicationCredentials{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.ApplicationCredential, *v1alpha1.ApplicationCredentialList, *apiv1alpha1.ApplicationCredentialApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("applicationcredentials"), + v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredential"), + func() *v1alpha1.ApplicationCredential { return &v1alpha1.ApplicationCredential{} }, + func() *v1alpha1.ApplicationCredentialList { return &v1alpha1.ApplicationCredentialList{} }, + func(dst, src *v1alpha1.ApplicationCredentialList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.ApplicationCredentialList) []*v1alpha1.ApplicationCredential { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.ApplicationCredentialList, items []*v1alpha1.ApplicationCredential) { + 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 fa80c4303..de60388f2 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 AddressScopeExpansion interface{} +type ApplicationCredentialExpansion interface{} + type DomainExpansion interface{} type EndpointExpansion interface{} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/applicationcredential.go b/pkg/clients/informers/externalversions/api/v1alpha1/applicationcredential.go new file mode 100644 index 000000000..a728706a7 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/applicationcredential.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" +) + +// ApplicationCredentialInformer provides access to a shared informer and lister for +// ApplicationCredentials. +type ApplicationCredentialInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.ApplicationCredentialLister +} + +type applicationCredentialInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewApplicationCredentialInformer constructs a new informer for ApplicationCredential 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 NewApplicationCredentialInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredApplicationCredentialInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredApplicationCredentialInformer constructs a new informer for ApplicationCredential 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 NewFilteredApplicationCredentialInformer(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().ApplicationCredentials(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().ApplicationCredentials(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().ApplicationCredentials(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().ApplicationCredentials(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.ApplicationCredential{}, + resyncPeriod, + indexers, + ) +} + +func (f *applicationCredentialInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredApplicationCredentialInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *applicationCredentialInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.ApplicationCredential{}, f.defaultInformer) +} + +func (f *applicationCredentialInformer) Lister() apiv1alpha1.ApplicationCredentialLister { + return apiv1alpha1.NewApplicationCredentialLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index 13fa48405..b9b415243 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 { // AddressScopes returns a AddressScopeInformer. AddressScopes() AddressScopeInformer + // ApplicationCredentials returns a ApplicationCredentialInformer. + ApplicationCredentials() ApplicationCredentialInformer // Domains returns a DomainInformer. Domains() DomainInformer // Endpoints returns a EndpointInformer. @@ -88,6 +90,11 @@ func (v *version) AddressScopes() AddressScopeInformer { return &addressScopeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// ApplicationCredentials returns a ApplicationCredentialInformer. +func (v *version) ApplicationCredentials() ApplicationCredentialInformer { + return &applicationCredentialInformer{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 40c16cf12..99f589164 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("addressscopes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().AddressScopes().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("applicationcredentials"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().ApplicationCredentials().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/applicationcredential.go b/pkg/clients/listers/api/v1alpha1/applicationcredential.go new file mode 100644 index 000000000..559f9b947 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/applicationcredential.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" +) + +// ApplicationCredentialLister helps list ApplicationCredentials. +// All objects returned here must be treated as read-only. +type ApplicationCredentialLister interface { + // List lists all ApplicationCredentials in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.ApplicationCredential, err error) + // ApplicationCredentials returns an object that can list and get ApplicationCredentials. + ApplicationCredentials(namespace string) ApplicationCredentialNamespaceLister + ApplicationCredentialListerExpansion +} + +// applicationCredentialLister implements the ApplicationCredentialLister interface. +type applicationCredentialLister struct { + listers.ResourceIndexer[*apiv1alpha1.ApplicationCredential] +} + +// NewApplicationCredentialLister returns a new ApplicationCredentialLister. +func NewApplicationCredentialLister(indexer cache.Indexer) ApplicationCredentialLister { + return &applicationCredentialLister{listers.New[*apiv1alpha1.ApplicationCredential](indexer, apiv1alpha1.Resource("applicationcredential"))} +} + +// ApplicationCredentials returns an object that can list and get ApplicationCredentials. +func (s *applicationCredentialLister) ApplicationCredentials(namespace string) ApplicationCredentialNamespaceLister { + return applicationCredentialNamespaceLister{listers.NewNamespaced[*apiv1alpha1.ApplicationCredential](s.ResourceIndexer, namespace)} +} + +// ApplicationCredentialNamespaceLister helps list and get ApplicationCredentials. +// All objects returned here must be treated as read-only. +type ApplicationCredentialNamespaceLister interface { + // List lists all ApplicationCredentials in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.ApplicationCredential, err error) + // Get retrieves the ApplicationCredential from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.ApplicationCredential, error) + ApplicationCredentialNamespaceListerExpansion +} + +// applicationCredentialNamespaceLister implements the ApplicationCredentialNamespaceLister +// interface. +type applicationCredentialNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.ApplicationCredential] +} diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 5328b091b..98590bb01 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -26,6 +26,14 @@ type AddressScopeListerExpansion interface{} // AddressScopeNamespaceLister. type AddressScopeNamespaceListerExpansion interface{} +// ApplicationCredentialListerExpansion allows custom methods to be added to +// ApplicationCredentialLister. +type ApplicationCredentialListerExpansion interface{} + +// ApplicationCredentialNamespaceListerExpansion allows custom methods to be added to +// ApplicationCredentialNamespaceLister. +type ApplicationCredentialNamespaceListerExpansion interface{} + // DomainListerExpansion allows custom methods to be added to // DomainLister. type DomainListerExpansion interface{} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 0d21d8ba6..6b3de36a5 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 - [AddressScope](#addressscope) +- [ApplicationCredential](#applicationcredential) - [Domain](#domain) - [Endpoint](#endpoint) - [Flavor](#flavor) @@ -256,12 +257,138 @@ _Appears in:_ | `mac` _string_ | mac contains a MAC address which a server connected to the port can
send packets with. | | MaxLength: 1024
Optional: \{\}
| +#### ApplicationCredential +ApplicationCredential is the Schema for an ORC resource. + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `ApplicationCredential` | | | +| `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` _[ApplicationCredentialSpec](#applicationcredentialspec)_ | spec specifies the desired state of the resource. | | | +| `status` _[ApplicationCredentialStatus](#applicationcredentialstatus)_ | status defines the observed state of the resource. | | | + + +#### ApplicationCredentialFilter + + + +ApplicationCredentialFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [ApplicationCredentialImport](#applicationcredentialimport) + +| 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
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
| + + +#### ApplicationCredentialImport + + + +ApplicationCredentialImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [ApplicationCredentialSpec](#applicationcredentialspec) + +| 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` _[ApplicationCredentialFilter](#applicationcredentialfilter)_ | 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
| + + +#### ApplicationCredentialResourceSpec + + + +ApplicationCredentialResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [ApplicationCredentialSpec](#applicationcredentialspec) + +| 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
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
| + + +#### ApplicationCredentialResourceStatus + + + +ApplicationCredentialResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [ApplicationCredentialStatus](#applicationcredentialstatus) + +| 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
| +| `userID` _string_ | userID is the ID of the User to which the resource is associated. | | MaxLength: 1024
| + + +#### ApplicationCredentialSpec + + + +ApplicationCredentialSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [ApplicationCredential](#applicationcredential) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[ApplicationCredentialImport](#applicationcredentialimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| +| `resource` _[ApplicationCredentialResourceSpec](#applicationcredentialresourcespec)_ | 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 | | | + + +#### ApplicationCredentialStatus + + + +ApplicationCredentialStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [ApplicationCredential](#applicationcredential) + +| 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` _[ApplicationCredentialResourceStatus](#applicationcredentialresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | + + #### AvailabilityZoneHint _Underlying type:_ _string_ @@ -309,6 +436,7 @@ CloudCredentialsReference is a reference to a secret containing OpenStack creden _Appears in:_ - [AddressScopeSpec](#addressscopespec) +- [ApplicationCredentialSpec](#applicationcredentialspec) - [DomainSpec](#domainspec) - [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) @@ -1997,6 +2125,7 @@ _Appears in:_ _Appears in:_ - [AddressScopeSpec](#addressscopespec) +- [ApplicationCredentialSpec](#applicationcredentialspec) - [DomainSpec](#domainspec) - [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) @@ -2035,6 +2164,7 @@ _Validation:_ _Appears in:_ - [AddressScopeSpec](#addressscopespec) +- [ApplicationCredentialSpec](#applicationcredentialspec) - [DomainSpec](#domainspec) - [EndpointSpec](#endpointspec) - [FlavorSpec](#flavorspec) From bab50920c744bf01b72042bac1548b657b7b611a Mon Sep 17 00:00:00 2001 From: Gondermann Date: Wed, 4 Feb 2026 14:36:40 +0100 Subject: [PATCH 129/237] Scaffolding for the ApplicationCredential controller Add the OpenStack client to scope On-behalf-of: SAP nils.gondermann@sap.com --- internal/scope/mock.go | 63 +++++++++++++++++++++----------------- internal/scope/provider.go | 4 +++ internal/scope/scope.go | 1 + 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 71bd8d8e6..256fa2e1d 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -34,26 +34,28 @@ 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 - GroupClient *mock.MockGroupClient - IdentityClient *mock.MockIdentityClient - ImageClient *mock.MockImageClient - KeyPairClient *mock.MockKeyPairClient - NetworkClient *mock.MockNetworkClient - RoleClient *mock.MockRoleClient - ServiceClient *mock.MockServiceClient - UserClient *mock.MockUserClient - VolumeClient *mock.MockVolumeClient - VolumeTypeClient *mock.MockVolumeTypeClient + AddressScope *mock.MockAddressScopeClient + ApplicationCredentialClient *mock.MockApplicationCredentialClient + ComputeClient *mock.MockComputeClient + DomainClient *mock.MockDomainClient + EndpointClient *mock.MockEndpointClient + GroupClient *mock.MockGroupClient + IdentityClient *mock.MockIdentityClient + ImageClient *mock.MockImageClient + KeyPairClient *mock.MockKeyPairClient + NetworkClient *mock.MockNetworkClient + RoleClient *mock.MockRoleClient + ServiceClient *mock.MockServiceClient + UserClient *mock.MockUserClient + VolumeClient *mock.MockVolumeClient + VolumeTypeClient *mock.MockVolumeTypeClient clientScopeCreateError error } func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { addressScope := mock.NewMockAddressScopeClient(mockCtrl) + applicationcredentialClient := mock.NewMockApplicationCredentialClient(mockCtrl) computeClient := mock.NewMockComputeClient(mockCtrl) domainClient := mock.NewMockDomainClient(mockCtrl) endpointClient := mock.NewMockEndpointClient(mockCtrl) @@ -69,20 +71,21 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) return &MockScopeFactory{ - AddressScope: addressScope, - ComputeClient: computeClient, - DomainClient: domainClient, - EndpointClient: endpointClient, - GroupClient: groupClient, - IdentityClient: identityClient, - ImageClient: imageClient, - KeyPairClient: keypairClient, - NetworkClient: networkClient, - RoleClient: roleClient, - ServiceClient: serviceClient, - UserClient: userClient, - VolumeClient: volumeClient, - VolumeTypeClient: volumetypeClient, + AddressScope: addressScope, + ApplicationCredentialClient: applicationcredentialClient, + ComputeClient: computeClient, + DomainClient: domainClient, + EndpointClient: endpointClient, + GroupClient: groupClient, + IdentityClient: identityClient, + ImageClient: imageClient, + KeyPairClient: keypairClient, + NetworkClient: networkClient, + RoleClient: roleClient, + ServiceClient: serviceClient, + UserClient: userClient, + VolumeClient: volumeClient, + VolumeTypeClient: volumetypeClient, } } @@ -153,6 +156,10 @@ func (f *MockScopeFactory) NewEndpointClient() (osclients.EndpointClient, error) return f.EndpointClient, nil } +func (f *MockScopeFactory) NewApplicationCredentialClient() (osclients.ApplicationCredentialClient, error) { + return f.ApplicationCredentialClient, 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 6f9902c39..aadd5c5ff 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -141,6 +141,10 @@ func (s *providerScope) NewAddressScopeClient() (clients.AddressScopeClient, err return clients.NewAddressScopeClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewApplicationCredentialClient() (clients.ApplicationCredentialClient, error) { + return clients.NewApplicationCredentialClient(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 6ed103308..d8426fd62 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -49,6 +49,7 @@ type Factory interface { // Scope contains arguments common to most operations. type Scope interface { NewAddressScopeClient() (osclients.AddressScopeClient, error) + NewApplicationCredentialClient() (osclients.ApplicationCredentialClient, error) NewComputeClient() (osclients.ComputeClient, error) NewDomainClient() (osclients.DomainClient, error) NewEndpointClient() (osclients.EndpointClient, error) From 59a1857267e3140fc6d148c9f08e84715ff8ed8b Mon Sep 17 00:00:00 2001 From: Gondermann Date: Wed, 4 Feb 2026 14:38:40 +0100 Subject: [PATCH 130/237] Scaffolding for the ApplicationCredential controller Register the controller On-behalf-of: SAP nils.gondermann@sap.com --- cmd/manager/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 25eae08f3..c8a624acc 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/addressscope" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/applicationcredential" "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" @@ -112,6 +113,7 @@ func main() { controllers := []interfaces.Controller{ addressscope.New(scopeFactory), + applicationcredential.New(scopeFactory), endpoint.New(scopeFactory), image.New(scopeFactory), network.New(scopeFactory), From 462446130d8e92fb70d4bf75267b0ca11ee52aba Mon Sep 17 00:00:00 2001 From: Gondermann Date: Wed, 4 Feb 2026 16:02:59 +0100 Subject: [PATCH 131/237] ApplicationCredential Type Declaration On-behalf-of: SAP nils.gondermann@sap.com --- api/v1alpha1/applicationcredential_types.go | 142 +++++++++++++++++--- 1 file changed, 121 insertions(+), 21 deletions(-) diff --git a/api/v1alpha1/applicationcredential_types.go b/api/v1alpha1/applicationcredential_types.go index 07e3889cc..a45805710 100644 --- a/api/v1alpha1/applicationcredential_types.go +++ b/api/v1alpha1/applicationcredential_types.go @@ -16,7 +16,42 @@ limitations under the License. package v1alpha1 +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +kubebuilder:validation:Enum:=CONNECT;DELETE;GET;HEAD;OPTIONS;PATCH;POST;PUT;TRACE +type HTTPMethod string + +const ( + HTTPMethodCONNECT HTTPMethod = "CONNECT" + HTTPMethodDELETE HTTPMethod = "DELETE" + HTTPMethodGET HTTPMethod = "GET" + HTTPMethodHEAD HTTPMethod = "HEAD" + HTTPMethodOPTIONS HTTPMethod = "OPTIONS" + HTTPMethodPATCH HTTPMethod = "PATCH" + HTTPMethodPOST HTTPMethod = "POST" + HTTPMethodPUT HTTPMethod = "PUT" + HTTPMethodTRACE HTTPMethod = "TRACE" +) + +// ApplicationCredentialAccessRule defines an access rule +// +kubebuilder:validation:MinProperties:=1 +type ApplicationCredentialAccessRule struct { + // path that the application credential is permitted to access + // +kubebuilder:validation:MaxLength=1024 + // +optional + Path *string `json:"path,omitempty"` + + // method that the application credential is permitted to use for a given API endpoint + // +optional + Method *HTTPMethod `json:"method,omitempty"` + + // serviceRef identifier for the service that the application credential is permitted to access + // +optional + ServiceRef *KubernetesNameRef `json:"serviceRef,omitempty"` +} + // ApplicationCredentialResourceSpec contains the desired state of the resource. +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ApplicationCredentialResourceSpec is immutable" type ApplicationCredentialResourceSpec struct { // name will be the name of the created resource. If not specified, the // name of the ORC object will be used. @@ -31,38 +66,87 @@ type ApplicationCredentialResourceSpec struct { // userRef is a reference to the ORC User which this resource is associated with. // +required - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable" UserRef KubernetesNameRef `json:"userRef,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/applicationcredentials - // - // 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"` + // unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts + // +optional + Unrestricted *bool `json:"unrestricted,omitempty"` + + // secretRef is a reference to a Secret containing the application credential secret + // +required + SecretRef KubernetesNameRef `json:"secretRef,omitempty"` + + // roleRefs may only contain roles that the user has assigned on the project. If not provided, the roles assigned to the application credential will be the same as the roles in the current token. + // +kubebuilder:validation:MaxItems:=256 + // +listType=atomic + // +optional + RoleRefs []KubernetesNameRef `json:"roleRefs,omitempty"` + + // accessRules is a list of fine grained access control rules + // +kubebuilder:validation:MaxItems:=256 + // +listType=atomic + // +optional + AccessRules []ApplicationCredentialAccessRule `json:"accessRules,omitempty"` + + // expiresAt is the time of expiration for the application credential. If unset, the application credential does not expire. + // +optional + ExpiresAt *metav1.Time `json:"expiresAt,omitempty"` } // ApplicationCredentialFilter defines an existing resource by its properties -// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MinProperties:=2 type ApplicationCredentialFilter struct { + // userRef is a reference to the ORC User which this resource is associated with. + // +required + UserRef KubernetesNameRef `json:"userRef,omitempty"` + // name of the existing resource // +optional Name *OpenStackName `json:"name,omitempty"` // description of the existing resource - // +kubebuilder:validation:MinLength:=1 - // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:MaxLength:=1024 // +optional Description *string `json:"description,omitempty"` +} - // userRef is a reference to the ORC User which this resource is associated with. +type ApplicationCredentialRoleStatus struct { + // name of an existing role + // +kubebuilder:validation:MaxLength:=1024 + // +optional + Name *string `json:"name,omitempty"` + + // id is the ID of a role + // +kubebuilder:validation:MaxLength:=1024 // +optional - UserRef *KubernetesNameRef `json:"userRef,omitempty"` + ID *string `json:"id,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/applicationcredentials + // domainID of the domain of this role + // +kubebuilder:validation:MaxLength:=1024 + // +optional + DomainID *string `json:"domainID,omitempty"` +} + +type ApplicationCredentialAccessRuleStatus struct { + // id is the ID of this access rule + // +kubebuilder:validation:MaxLength:=1024 + // +optional + ID *string `json:"id,omitempty"` + + // path that the application credential is permitted to access + // +kubebuilder:validation:MaxLength:=1024 + // +optional + Path *string `json:"path,omitempty"` + + // method that the application credential is permitted to use for a given API endpoint + // +kubebuilder:validation:MaxLength=32 + // +optional + Method *string `json:"method,omitempty"` + + // service type identifier for the service that the application credential is permitted to access + // +kubebuilder:validation:MaxLength:=1024 + // +optional + Service *string `json:"service,omitempty"` } // ApplicationCredentialResourceStatus represents the observed state of the resource. @@ -77,12 +161,28 @@ type ApplicationCredentialResourceStatus struct { // +optional Description string `json:"description,omitempty"` - // userID is the ID of the User to which the resource is associated. + // unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts + // +optional + Unrestricted bool `json:"unrestricted,omitempty"` + + // projectID of the project the application credential was created for and that authentication requests using this application credential will be scoped to. // +kubebuilder:validation:MaxLength=1024 // +optional - UserID string `json:"userID,omitempty"` + ProjectID string `json:"projectID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ApplicationCredential structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials + // roles is a list of role objects may only contain roles that the user has assigned on the project + // +kubebuilder:validation:MaxItems:=64 + // +listType=atomic + // +optional + Roles []ApplicationCredentialRoleStatus `json:"roles"` + + // expiresAt is the time of expiration for the application credential. If unset, the application credential does not expire. + // +optional + ExpiresAt *metav1.Time `json:"expiresAt"` + + // accessRules is a list of fine grained access control rules + // +kubebuilder:validation:MaxItems:=64 + // +listType=atomic + // +optional + AccessRules []ApplicationCredentialAccessRuleStatus `json:"accessRules,omitempty"` } From e1c7b0d8bd8e3069de670c77203b11ecc9bc06cd Mon Sep 17 00:00:00 2001 From: Gondermann Date: Tue, 10 Feb 2026 12:19:18 +0100 Subject: [PATCH 132/237] ApplicationCredential Workaround for userID requirement in OpenStack API On-behalf-of: SAP nils.gondermann@sap.com --- internal/osclients/applicationcredential.go | 67 ++++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/internal/osclients/applicationcredential.go b/internal/osclients/applicationcredential.go index 1dcda69a9..2742093fa 100644 --- a/internal/osclients/applicationcredential.go +++ b/internal/osclients/applicationcredential.go @@ -18,12 +18,16 @@ package osclients import ( "context" + "errors" "fmt" "iter" + tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users" "github.com/gophercloud/utils/v2/openstack/clientconfig" ) @@ -66,12 +70,69 @@ func (c applicationcredentialClient) DeleteApplicationCredential(ctx context.Con return applicationcredentials.Delete(ctx, c.client, resourceID).ExtractErr() } +func (c applicationcredentialClient) UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + return applicationcredentials.Update(ctx, c.client, id, opts).Extract() +} + func (c applicationcredentialClient) GetApplicationCredential(ctx context.Context, resourceID string) (*applicationcredentials.ApplicationCredential, error) { - return applicationcredentials.Get(ctx, c.client, resourceID).Extract() + // The unique ID of an application credential is not enough to query it from OpenStack + // OpenStack actually also requires a unique user ID. + // We can not provide the user ID here, as the function signatures of ORC interfaces + // expect us to return an OpenStack resource based on a single string. + + // To work around this, we first query ApplicationCredentials for the currently + // authenticated user which ORC is connected as. If that fails, we iterate over + // all users we have access to and query their ApplicationCredentials. + + // Currently authenticated user + userID, err := GetAuthenticatedUserID(c.client.ProviderClient) + if err == nil { + appCred, appCredErr := applicationcredentials.Get(ctx, c.client, userID, resourceID).Extract() + + if appCred != nil { + return appCred, appCredErr + } + } + + // If not found in currently authenticated user, try iterating over all users + userPager := users.List(c.client, nil) + userIterator := func(yield func(*users.User, error) bool) { + _ = userPager.EachPage(ctx, yieldPage(users.ExtractUsers, yield)) + } + + for user, userErr := range userIterator { + if userErr != nil { + continue + } + + appCred, appCredErr := applicationcredentials.Get(ctx, c.client, user.ID, resourceID).Extract() + + if appCred != nil { + return appCred, appCredErr + } + } + + return nil, gophercloud.ErrResourceNotFound{ + Name: resourceID, + ResourceType: "ApplicationCredential", + } } -func (c applicationcredentialClient) UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { - return applicationcredentials.Update(ctx, c.client, id, opts).Extract() +func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) { + r := providerClient.GetAuthResult() + if r == nil { + return "", errors.New("no AuthResult available") + } + switch r := r.(type) { + case tokens3.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + default: + return "", errors.New("wrong AuthResult version") + } } type applicationcredentialErrorClient struct{ error } From 0766932ea4a1c2b70287dd0bfd7a5cfbdc8a03db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 10 Apr 2026 08:19:39 +0200 Subject: [PATCH 133/237] Project: Add ability to specify domainRef Allow users to specify which Keystone domain a Project should be created under by adding a `domainRef` field to the spec and filter. The domain is is now returned in the project status as well. Fixes https://github.com/k-orc/openstack-resource-controller/issues/739. --- api/v1alpha1/project_types.go | 14 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 10 ++++ cmd/models-schema/zz_generated.openapi.go | 21 +++++++++ .../bases/openstack.k-orc.cloud_projects.yaml | 20 ++++++++ internal/controllers/project/actuator.go | 41 ++++++++++++++++- internal/controllers/project/controller.go | 46 +++++++++++++++++++ internal/controllers/project/status.go | 1 + .../tests/project-create-full/00-assert.yaml | 22 +++++++++ .../00-create-resource.yaml | 12 +++++ .../tests/project-create-full/01-assert.yaml | 10 ++++ .../01-disable-domain.yaml | 7 +++ .../tests/project-create-full/README.md | 6 +++ .../tests/project-dependency/00-assert.yaml | 15 ++++++ .../00-create-resources-missing-deps.yaml | 12 +++++ .../tests/project-dependency/00-secret.yaml | 6 +++ .../tests/project-dependency/01-assert.yaml | 29 ++++++++++++ .../01-create-dependencies.yaml | 12 +++++ .../tests/project-dependency/02-assert.yaml | 9 ++-- .../project-dependency/02-disable-domain.yaml | 8 ++++ .../tests/project-dependency/03-assert.yaml | 19 ++++++-- ...ncies.yaml => 03-delete-dependencies.yaml} | 3 ++ .../tests/project-dependency/04-assert.yaml | 9 ++++ ...esources.yaml => 04-delete-resources.yaml} | 4 ++ .../tests/project-dependency/README.md | 6 ++- .../project-import-dependency/00-assert.yaml | 17 +++++++ .../00-import-resource.yaml | 26 +++++++++++ .../project-import-dependency/00-secret.yaml | 6 +++ .../project-import-dependency/01-assert.yaml | 32 +++++++++++++ .../01-create-trap-resource.yaml | 24 ++++++++++ .../project-import-dependency/02-assert.yaml | 34 ++++++++++++++ .../02-create-resource.yaml | 23 ++++++++++ .../project-import-dependency/03-assert.yaml | 15 ++++++ .../03-disable-domain.yaml | 16 +++++++ .../project-import-dependency/04-assert.yaml | 6 +++ .../04-delete-import-dependencies.yaml | 7 +++ .../project-import-dependency/05-assert.yaml | 6 +++ .../05-delete-resource.yaml | 7 +++ .../tests/project-import-dependency/README.md | 33 +++++++++++++ .../api/v1alpha1/projectfilter.go | 11 ++++- .../api/v1alpha1/projectresourcespec.go | 17 +++++-- .../api/v1alpha1/projectresourcestatus.go | 9 ++++ .../applyconfiguration/internal/internal.go | 9 ++++ test/apivalidations/project_test.go | 12 +++++ website/docs/crd-reference.md | 5 ++ 44 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 internal/controllers/project/tests/project-create-full/01-assert.yaml create mode 100644 internal/controllers/project/tests/project-create-full/01-disable-domain.yaml create mode 100644 internal/controllers/project/tests/project-dependency/00-secret.yaml create mode 100644 internal/controllers/project/tests/project-dependency/02-disable-domain.yaml rename internal/controllers/project/tests/project-dependency/{02-delete-dependencies.yaml => 03-delete-dependencies.yaml} (70%) create mode 100644 internal/controllers/project/tests/project-dependency/04-assert.yaml rename internal/controllers/project/tests/project-dependency/{03-delete-resources.yaml => 04-delete-resources.yaml} (59%) create mode 100644 internal/controllers/project/tests/project-import-dependency/00-assert.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/00-secret.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/01-assert.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/02-assert.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/03-assert.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/03-disable-domain.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/04-assert.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/04-delete-import-dependencies.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/05-assert.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/05-delete-resource.yaml create mode 100644 internal/controllers/project/tests/project-import-dependency/README.md diff --git a/api/v1alpha1/project_types.go b/api/v1alpha1/project_types.go index 3fa321c79..0c49011c9 100644 --- a/api/v1alpha1/project_types.go +++ b/api/v1alpha1/project_types.go @@ -64,6 +64,11 @@ type ProjectResourceSpec struct { // +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"` + // enabled defines whether a project is enabled or not. Default is true. // +optional Enabled *bool `json:"enabled,omitempty"` @@ -83,6 +88,10 @@ type ProjectFilter struct { // +optional Name *KeystoneName `json:"name,omitempty"` + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + FilterByKeystoneTags `json:",inline"` } @@ -98,6 +107,11 @@ type ProjectResourceStatus struct { // +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"` + // enabled represents whether a project is enabled or not. // +optional Enabled *bool `json:"enabled,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index dffa3ec73..696af6ca1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3149,6 +3149,11 @@ func (in *ProjectFilter) DeepCopyInto(out *ProjectFilter) { *out = new(KeystoneName) **out = **in } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } in.FilterByKeystoneTags.DeepCopyInto(&out.FilterByKeystoneTags) } @@ -3232,6 +3237,11 @@ func (in *ProjectResourceSpec) DeepCopyInto(out *ProjectResourceSpec) { *out = new(string) **out = **in } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } if in.Enabled != nil { in, out := &in.Enabled, &out.Enabled *out = new(bool) diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 2c1ee063e..3642ebe37 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -6080,6 +6080,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProjectFilter(ref comm Format: "", }, }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, "tags": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -6266,6 +6273,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceSpec(re Format: "", }, }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef is a reference to the ORC Domain which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, "enabled": { SchemaProps: spec.SchemaProps{ Description: "enabled defines whether a project is enabled or not. Default is true.", @@ -6320,6 +6334,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceStatus( Format: "", }, }, + "domainID": { + SchemaProps: spec.SchemaProps{ + Description: "domainID is the ID of the Domain to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, "enabled": { SchemaProps: spec.SchemaProps{ Description: "enabled represents whether a project is enabled or not.", diff --git a/config/crd/bases/openstack.k-orc.cloud_projects.yaml b/config/crd/bases/openstack.k-orc.cloud_projects.yaml index 4cc284afc..e673b6217 100644 --- a/config/crd/bases/openstack.k-orc.cloud_projects.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_projects.yaml @@ -91,6 +91,12 @@ spec: 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: 64 @@ -195,6 +201,15 @@ spec: maxLength: 65535 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 project is enabled or not. Default is true. @@ -326,6 +341,11 @@ spec: resource. maxLength: 65535 type: string + domainID: + description: domainID is the ID of the Domain to which the resource + is associated. + maxLength: 1024 + type: string enabled: description: enabled represents whether a project is enabled or not. diff --git a/internal/controllers/project/actuator.go b/internal/controllers/project/actuator.go index 2b882ae91..ca156cee0 100644 --- a/internal/controllers/project/actuator.go +++ b/internal/controllers/project/actuator.go @@ -25,11 +25,13 @@ import ( 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" generic "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/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" ) @@ -53,7 +55,8 @@ type projectClient interface { } type projectActuator struct { - osClient projectClient + osClient projectClient + k8sClient client.Client } var _ createResourceActuator = projectActuator{} @@ -85,8 +88,23 @@ func (actuator projectActuator) ListOSResourcesForAdoption(ctx context.Context, } func (actuator projectActuator) ListOSResourcesForImport(ctx context.Context, orcObject orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + var reconcileStatus progress.ReconcileStatus + + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.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 := projects.ListOpts{ Name: string(ptr.Deref(filter.Name, "")), + DomainID: ptr.Deref(domain.Status.ID, ""), Tags: tags.Join(filter.Tags), TagsAny: tags.Join(filter.TagsAny), NotTags: tags.Join(filter.NotTags), @@ -104,6 +122,23 @@ func (actuator projectActuator) CreateResource(ctx context.Context, obj orcObjec 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, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } tags := make([]string, len(resource.Tags)) for i := range resource.Tags { @@ -115,6 +150,7 @@ func (actuator projectActuator) CreateResource(ctx context.Context, obj orcObjec createOpts := projects.CreateOpts{ Name: getResourceName(obj), Description: ptr.Deref(resource.Description, ""), + DomainID: domainID, Enabled: resource.Enabled, Tags: tags, } @@ -251,7 +287,8 @@ func newActuator(ctx context.Context, orcObject *orcv1alpha1.Project, controller } return projectActuator{ - osClient: osClient, + osClient: osClient, + k8sClient: controller.GetK8sClient(), }, nil } diff --git a/internal/controllers/project/controller.go b/internal/controllers/project/controller.go index 8112555ae..116b2024e 100644 --- a/internal/controllers/project/controller.go +++ b/internal/controllers/project/controller.go @@ -21,6 +21,7 @@ import ( "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" @@ -29,6 +30,8 @@ import ( "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 = "project" @@ -48,15 +51,58 @@ func (projectReconcilerConstructor) GetName() string { return controllerName } +var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ProjectList, *orcv1alpha1.Domain]( + "spec.resource.domainRef", + func(project *orcv1alpha1.Project) []string { + resource := project.Spec.Resource + if resource == nil || resource.DomainRef == nil { + return nil + } + return []string{string(*resource.DomainRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var domainImportDependency = dependency.NewDependency[*orcv1alpha1.ProjectList, *orcv1alpha1.Domain]( + "spec.import.filter.domainRef", + func(project *orcv1alpha1.Project) []string { + resource := project.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 projectReconcilerConstructor) 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 + } + + 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{})), + ). + // 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.Project{}) if err := errors.Join( + domainDependency.AddToManager(ctx, mgr), + domainImportDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/project/status.go b/internal/controllers/project/status.go index 15ed56656..9ff6ef10c 100644 --- a/internal/controllers/project/status.go +++ b/internal/controllers/project/status.go @@ -54,6 +54,7 @@ func (projectStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Projec func (projectStatusWriter) ApplyResourceStatus(_ logr.Logger, osResource *projects.Project, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.ProjectResourceStatus(). WithName(osResource.Name). + WithDomainID(osResource.DomainID). WithEnabled(osResource.Enabled). WithTags(osResource.Tags...) if osResource.Description != "" { diff --git a/internal/controllers/project/tests/project-create-full/00-assert.yaml b/internal/controllers/project/tests/project-create-full/00-assert.yaml index 25708cfc1..4e38b074a 100644 --- a/internal/controllers/project/tests/project-create-full/00-assert.yaml +++ b/internal/controllers/project/tests/project-create-full/00-assert.yaml @@ -11,3 +11,25 @@ status: tags: - tag1 - tag2 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-create-full + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: project-create-full + ref: domain +assertAll: + - celExpr: "project.status.id != ''" + - celExpr: "project.status.resource.domainID == domain.status.id" diff --git a/internal/controllers/project/tests/project-create-full/00-create-resource.yaml b/internal/controllers/project/tests/project-create-full/00-create-resource.yaml index d705e1f6a..0727971b1 100644 --- a/internal/controllers/project/tests/project-create-full/00-create-resource.yaml +++ b/internal/controllers/project/tests/project-create-full/00-create-resource.yaml @@ -1,5 +1,16 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project metadata: name: project-create-full @@ -11,6 +22,7 @@ spec: resource: name: project-create-full-override description: Project from "create full" test + domainRef: project-create-full enabled: false tags: - tag1 diff --git a/internal/controllers/project/tests/project-create-full/01-assert.yaml b/internal/controllers/project/tests/project-create-full/01-assert.yaml new file mode 100644 index 000000000..8a73533e9 --- /dev/null +++ b/internal/controllers/project/tests/project-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: project-create-full + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" diff --git a/internal/controllers/project/tests/project-create-full/01-disable-domain.yaml b/internal/controllers/project/tests/project-create-full/01-disable-domain.yaml new file mode 100644 index 000000000..d2c28b34b --- /dev/null +++ b/internal/controllers/project/tests/project-create-full/01-disable-domain.yaml @@ -0,0 +1,7 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-create-full +spec: + resource: + enabled: false diff --git a/internal/controllers/project/tests/project-create-full/README.md b/internal/controllers/project/tests/project-create-full/README.md index abf194158..0e059b6c5 100644 --- a/internal/controllers/project/tests/project-create-full/README.md +++ b/internal/controllers/project/tests/project-create-full/README.md @@ -6,6 +6,12 @@ Create a project using all available fields, and verify that the observed state Also validate that the OpenStack resource uses the name from the spec when it is specified. +## Step 01 + +By default the enabled field is set to true, the enabled field needs to be disabled. + +Disabling the Domain is required before deletion in Openstack. + ## Reference https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/project/tests/project-dependency/00-assert.yaml b/internal/controllers/project/tests/project-dependency/00-assert.yaml index a314a6124..a49448cee 100644 --- a/internal/controllers/project/tests/project-dependency/00-assert.yaml +++ b/internal/controllers/project/tests/project-dependency/00-assert.yaml @@ -13,3 +13,18 @@ status: message: Waiting for Secret/project-dependency to be created status: "True" reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-dependency-no-domain +status: + conditions: + - type: Available + message: Waiting for Domain/project-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Domain/project-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/project/tests/project-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/project/tests/project-dependency/00-create-resources-missing-deps.yaml index 075fcff47..eb1fedfca 100644 --- a/internal/controllers/project/tests/project-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/project/tests/project-dependency/00-create-resources-missing-deps.yaml @@ -1,6 +1,18 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project +metadata: + name: project-dependency-no-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: project-dependency +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project metadata: name: project-dependency-no-secret spec: diff --git a/internal/controllers/project/tests/project-dependency/00-secret.yaml b/internal/controllers/project/tests/project-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/project/tests/project-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/project/tests/project-dependency/01-assert.yaml b/internal/controllers/project/tests/project-dependency/01-assert.yaml index 0102e1a6e..b018d94db 100644 --- a/internal/controllers/project/tests/project-dependency/01-assert.yaml +++ b/internal/controllers/project/tests/project-dependency/01-assert.yaml @@ -13,3 +13,32 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-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: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-dependency-no-domain + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: project-dependency + ref: domain +assertAll: + - celExpr: "project.status.resource.domainID == domain.status.id" diff --git a/internal/controllers/project/tests/project-dependency/01-create-dependencies.yaml b/internal/controllers/project/tests/project-dependency/01-create-dependencies.yaml index 8aaf12e85..13526f11c 100644 --- a/internal/controllers/project/tests/project-dependency/01-create-dependencies.yaml +++ b/internal/controllers/project/tests/project-dependency/01-create-dependencies.yaml @@ -1,5 +1,17 @@ +--- apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - command: kubectl create secret generic project-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} diff --git a/internal/controllers/project/tests/project-dependency/02-assert.yaml b/internal/controllers/project/tests/project-dependency/02-assert.yaml index c41563a86..420d34a33 100644 --- a/internal/controllers/project/tests/project-dependency/02-assert.yaml +++ b/internal/controllers/project/tests/project-dependency/02-assert.yaml @@ -2,10 +2,9 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - - apiVersion: v1 - kind: Secret + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain name: project-dependency - ref: secret + ref: domain assertAll: - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/project' in secret.metadata.finalizers" + - celExpr: "domain.status.resource.enabled == false" diff --git a/internal/controllers/project/tests/project-dependency/02-disable-domain.yaml b/internal/controllers/project/tests/project-dependency/02-disable-domain.yaml new file mode 100644 index 000000000..fde38804b --- /dev/null +++ b/internal/controllers/project/tests/project-dependency/02-disable-domain.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-dependency +spec: + resource: + enabled: false diff --git a/internal/controllers/project/tests/project-dependency/03-assert.yaml b/internal/controllers/project/tests/project-dependency/03-assert.yaml index 763703c4c..e4953f1ed 100644 --- a/internal/controllers/project/tests/project-dependency/03-assert.yaml +++ b/internal/controllers/project/tests/project-dependency/03-assert.yaml @@ -1,6 +1,17 @@ +--- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -commands: -# Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get secret project-dependency --namespace $NAMESPACE" - skipLogOutput: true +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: project-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: project-dependency + ref: secret +assertAll: + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/project' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/project' in secret.metadata.finalizers" diff --git a/internal/controllers/project/tests/project-dependency/02-delete-dependencies.yaml b/internal/controllers/project/tests/project-dependency/03-delete-dependencies.yaml similarity index 70% rename from internal/controllers/project/tests/project-dependency/02-delete-dependencies.yaml rename to internal/controllers/project/tests/project-dependency/03-delete-dependencies.yaml index 43755b780..f91ab7b46 100644 --- a/internal/controllers/project/tests/project-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/project/tests/project-dependency/03-delete-dependencies.yaml @@ -1,6 +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 domain project-dependency --wait=false + namespaced: true - command: kubectl delete secret project-dependency --wait=false namespaced: true diff --git a/internal/controllers/project/tests/project-dependency/04-assert.yaml b/internal/controllers/project/tests/project-dependency/04-assert.yaml new file mode 100644 index 000000000..13dcb5726 --- /dev/null +++ b/internal/controllers/project/tests/project-dependency/04-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 domain project-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret project-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/project/tests/project-dependency/03-delete-resources.yaml b/internal/controllers/project/tests/project-dependency/04-delete-resources.yaml similarity index 59% rename from internal/controllers/project/tests/project-dependency/03-delete-resources.yaml rename to internal/controllers/project/tests/project-dependency/04-delete-resources.yaml index 7350e392a..95e368a8b 100644 --- a/internal/controllers/project/tests/project-dependency/03-delete-resources.yaml +++ b/internal/controllers/project/tests/project-dependency/04-delete-resources.yaml @@ -1,6 +1,10 @@ +--- apiVersion: kuttl.dev/v1beta1 kind: TestStep delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project name: project-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-dependency-no-domain diff --git a/internal/controllers/project/tests/project-dependency/README.md b/internal/controllers/project/tests/project-dependency/README.md index c0fd0da71..1953a2fed 100644 --- a/internal/controllers/project/tests/project-dependency/README.md +++ b/internal/controllers/project/tests/project-dependency/README.md @@ -10,10 +10,14 @@ Create the missing dependencies and make and verify all the projects are availab ## 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 projects and validate that all resources are gone. ## Reference diff --git a/internal/controllers/project/tests/project-import-dependency/00-assert.yaml b/internal/controllers/project/tests/project-import-dependency/00-assert.yaml new file mode 100644 index 000000000..00232e039 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/00-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Domain/project-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Domain/project-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/project/tests/project-import-dependency/00-import-resource.yaml b/internal/controllers/project/tests/project-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..d7845ab96 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/00-import-resource.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: project-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + domainRef: project-import-dependency diff --git a/internal/controllers/project/tests/project-import-dependency/00-secret.yaml b/internal/controllers/project/tests/project-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/project/tests/project-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/project/tests/project-import-dependency/01-assert.yaml b/internal/controllers/project/tests/project-import-dependency/01-assert.yaml new file mode 100644 index 000000000..80aaf3561 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/01-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-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: Project +metadata: + name: project-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Domain/project-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Domain/project-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/project/tests/project-import-dependency/01-create-trap-resource.yaml b/internal/controllers/project/tests/project-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..85d3675f9 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-import-dependency-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# This `project-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-import-dependency-not-this-one +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: project-import-dependency-not-this-one diff --git a/internal/controllers/project/tests/project-import-dependency/02-assert.yaml b/internal/controllers/project/tests/project-import-dependency/02-assert.yaml new file mode 100644 index 000000000..b07fcffd9 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/02-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-import-dependency + ref: project1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-import-dependency-not-this-one + ref: project2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: project-import-dependency + ref: domain +assertAll: + - celExpr: "project1.status.id != project2.status.id" + - celExpr: "project1.status.resource.domainID == domain.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-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/project/tests/project-import-dependency/02-create-resource.yaml b/internal/controllers/project/tests/project-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..b4f7df6a6 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/02-create-resource.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-import-dependency-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: project-import-dependency-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + domainRef: project-import-dependency-external diff --git a/internal/controllers/project/tests/project-import-dependency/03-assert.yaml b/internal/controllers/project/tests/project-import-dependency/03-assert.yaml new file mode 100644 index 000000000..88ebac977 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/03-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: project-import-dependency-external + ref: domain1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: project-import-dependency-not-this-one + ref: domain2 +assertAll: + - celExpr: "domain1.status.resource.enabled == false" + - celExpr: "domain2.status.resource.enabled == false" diff --git a/internal/controllers/project/tests/project-import-dependency/03-disable-domain.yaml b/internal/controllers/project/tests/project-import-dependency/03-disable-domain.yaml new file mode 100644 index 000000000..163393f1a --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/03-disable-domain.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-import-dependency-external +spec: + resource: + enabled: false +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: project-import-dependency-not-this-one +spec: + resource: + enabled: false diff --git a/internal/controllers/project/tests/project-import-dependency/04-assert.yaml b/internal/controllers/project/tests/project-import-dependency/04-assert.yaml new file mode 100644 index 000000000..4aef05790 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get domain project-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/project/tests/project-import-dependency/04-delete-import-dependencies.yaml b/internal/controllers/project/tests/project-import-dependency/04-delete-import-dependencies.yaml new file mode 100644 index 000000000..2751ea3af --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/04-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 project-import-dependency + namespaced: true diff --git a/internal/controllers/project/tests/project-import-dependency/05-assert.yaml b/internal/controllers/project/tests/project-import-dependency/05-assert.yaml new file mode 100644 index 000000000..a81cc4a09 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/05-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get project project-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/project/tests/project-import-dependency/05-delete-resource.yaml b/internal/controllers/project/tests/project-import-dependency/05-delete-resource.yaml new file mode 100644 index 000000000..9a22a4915 --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/05-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: project-import-dependency diff --git a/internal/controllers/project/tests/project-import-dependency/README.md b/internal/controllers/project/tests/project-import-dependency/README.md new file mode 100644 index 000000000..f025cda7c --- /dev/null +++ b/internal/controllers/project/tests/project-import-dependency/README.md @@ -0,0 +1,33 @@ +# Check dependency handling for imported Project + +## Step 00 + +Import a Project that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the Project is waiting for the dependency to be ready. + +## Step 01 + +Create a Project matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a Project matching the import filters. + +Verify that the observed status on the imported Project corresponds to the spec of the created Project. + +## 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 Project and validate that all resources are gone. + +## Step 05 + +Disable the domain dependencies so KUTTL can clean the resources without failing. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go index 90b315787..f7538e1c1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectfilter.go @@ -25,7 +25,8 @@ import ( // ProjectFilterApplyConfiguration represents a declarative configuration of the ProjectFilter type for use // with apply. type ProjectFilterApplyConfiguration struct { - Name *apiv1alpha1.KeystoneName `json:"name,omitempty"` + Name *apiv1alpha1.KeystoneName `json:"name,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` FilterByKeystoneTagsApplyConfiguration `json:",inline"` } @@ -43,6 +44,14 @@ func (b *ProjectFilterApplyConfiguration) WithName(value apiv1alpha1.KeystoneNam 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 *ProjectFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *ProjectFilterApplyConfiguration { + b.DomainRef = &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/projectresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go index 8a5ef4250..0d49ae3ae 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcespec.go @@ -25,10 +25,11 @@ import ( // ProjectResourceSpecApplyConfiguration represents a declarative configuration of the ProjectResourceSpec type for use // with apply. type ProjectResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.KeystoneName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Tags []apiv1alpha1.KeystoneTag `json:"tags,omitempty"` + Name *apiv1alpha1.KeystoneName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Tags []apiv1alpha1.KeystoneTag `json:"tags,omitempty"` } // ProjectResourceSpecApplyConfiguration constructs a declarative configuration of the ProjectResourceSpec type for use with @@ -53,6 +54,14 @@ func (b *ProjectResourceSpecApplyConfiguration) WithDescription(value string) *P 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 *ProjectResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *ProjectResourceSpecApplyConfiguration { + b.DomainRef = &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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go index 0e2ad1f94..4a7fdb3cc 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectresourcestatus.go @@ -23,6 +23,7 @@ package v1alpha1 type ProjectResourceStatusApplyConfiguration struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` + DomainID *string `json:"domainID,omitempty"` Enabled *bool `json:"enabled,omitempty"` Tags []string `json:"tags,omitempty"` } @@ -49,6 +50,14 @@ func (b *ProjectResourceStatusApplyConfiguration) WithDescription(value string) 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 *ProjectResourceStatusApplyConfiguration) WithDomainID(value string) *ProjectResourceStatusApplyConfiguration { + b.DomainID = &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. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 84688dc8a..7a30ca985 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1737,6 +1737,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ProjectFilter map: fields: + - name: domainRef + type: + scalar: string - name: name type: scalar: string @@ -1779,6 +1782,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: description type: scalar: string + - name: domainRef + type: + scalar: string - name: enabled type: scalar: boolean @@ -1797,6 +1803,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: description type: scalar: string + - name: domainID + type: + scalar: string - name: enabled type: scalar: boolean diff --git a/test/apivalidations/project_test.go b/test/apivalidations/project_test.go index 383f817c5..745a38400 100644 --- a/test/apivalidations/project_test.go +++ b/test/apivalidations/project_test.go @@ -106,4 +106,16 @@ var _ = Describe("ORC Project API validations", func() { WithTags("foo", "bar")) Expect(applyObj(ctx, project, patch)).To(Succeed()) }) + + It("should have immutable domainRef", func(ctx context.Context) { + project := projectStub(namespace) + patch := baseProjectPatch(project) + patch.Spec.WithResource(applyconfigv1alpha1.ProjectResourceSpec(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, project, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.ProjectResourceSpec(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, project, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index aee5bb6d7..645c9cd3f 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1927,6 +1927,8 @@ _Appears in:_ - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) - [PortResourceSpec](#portresourcespec) +- [ProjectFilter](#projectfilter) +- [ProjectResourceSpec](#projectresourcespec) - [RoleFilter](#rolefilter) - [RoleResourceSpec](#roleresourcespec) - [RouterFilter](#routerfilter) @@ -2631,6 +2633,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `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: \{\}
| | `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: \{\}
| @@ -2672,6 +2675,7 @@ _Appears in:_ | --- | --- | --- | --- | | `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: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
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: \{\}
| @@ -2691,6 +2695,7 @@ _Appears in:_ | --- | --- | --- | --- | | `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: \{\}
| +| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
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: \{\}
| From f6ddf5f3ad7877d87aa5f97a1db78e6353ad0429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 10 Apr 2026 08:40:38 +0200 Subject: [PATCH 134/237] Bump go to v1.25.9 Fixes a few vulnerabilities: - GO-2026-4865 - GO-2026-4870 - GO-2026-4946 - GO-2026-4947 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f2e20f11a..518e39318 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.69.3 -GO_VERSION ?= 1.25.8 +GO_VERSION ?= 1.25.9 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) From f8226a71755b7d2769580e5d8de0fda1e2a1a285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 10 Apr 2026 08:44:01 +0200 Subject: [PATCH 135/237] Bump go.opentelemetry.io/otel/sdk Fixes CVE-2026-39883. --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 4a671e866..a7f57b441 100644 --- a/go.mod +++ b/go.mod @@ -72,12 +72,12 @@ require ( github.com/x448/float16 v0.8.4 // 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.41.0 // indirect + go.opentelemetry.io/otel v1.43.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.41.0 // indirect - go.opentelemetry.io/otel/sdk v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.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 diff --git a/go.sum b/go.sum index 34971a3f3..40912761b 100644 --- a/go.sum +++ b/go.sum @@ -172,20 +172,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ 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.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= 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.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/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= 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= From 46e801de98c37b65b77943862433e3ad971300a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 13 Apr 2026 10:59:17 +0200 Subject: [PATCH 136/237] CI: Add gazpacho, drop dalmatian Dalmatian is about to go EOL later this month. --- .github/workflows/e2e.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 349c89e60..b4df37a56 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -14,15 +14,15 @@ jobs: fail-fast: false matrix: include: + - name: "gazpacho" + openstack_version: "stable/2026.1" + ubuntu_version: "24.04" - name: "flamingo" openstack_version: "stable/2025.2" ubuntu_version: "24.04" - name: "epoxy" openstack_version: "stable/2025.1" ubuntu_version: "24.04" - - name: "dalmatian" - openstack_version: "stable/2024.2" - ubuntu_version: "22.04" env: image_tag: virtual-registry.k-orc.cloud/ci:commit-${GITHUB_SHA::7} From c805064db8964c40c888cc1e205403a26cb5f9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 13 Apr 2026 15:24:54 +0200 Subject: [PATCH 137/237] CI: Fix floating version tag comments in GitHub Actions workflows Consistently use precise version tags instead of floating major version tags. --- .github/workflows/container_image.yaml | 2 +- .github/workflows/e2e.yaml | 2 +- .github/workflows/ensure-labels.yaml | 2 +- .github/workflows/label-pr.yaml | 2 +- .github/workflows/release_image.yaml | 2 +- .github/workflows/semver.yaml | 2 +- .github/workflows/website.yaml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index 5a6911304..95d8eaaf5 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -25,7 +25,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4.0.0 - name: Build and push images run: | diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 349c89e60..caf31cef2 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -68,7 +68,7 @@ jobs: - name: Upload logs artifacts on failure if: failure() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7.0.0 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 bb1f461ed..32d561030 100644 --- a/.github/workflows/ensure-labels.yaml +++ b/.github/workflows/ensure-labels.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 with: persist-credentials: false - - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # tag=v1 + - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # tag=v1.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 723dc1f87..32383da6c 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -81,4 +81,4 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # tag=v6 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # tag=v6.0.1 diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index 8fbf2f332..7a9319faa 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -25,7 +25,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4.0.0 - name: Install build dependencies run: sudo apt-get install -y libgpgme-dev diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 42c5d6a8c..9ff27d751 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -62,7 +62,7 @@ jobs: - name: Upload semver results if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7.0.0 with: name: semver-results path: semver-results/ diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 1ee7dd42e..81c385abf 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -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@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # tag=v1 + uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # tag=v1.5.0 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} # zizmor: ignore[secrets-outside-env] accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} # zizmor: ignore[secrets-outside-env] From 7d548af009e55f60a6549630bfe82d3dbfb5cd7c Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Tue, 3 Feb 2026 18:04:23 +0200 Subject: [PATCH 138/237] Enhancement: Add drift detection and automatic reconciliation Proposal for drift detection feature. --- enhancements/drift-detection.md | 277 ++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 enhancements/drift-detection.md diff --git a/enhancements/drift-detection.md b/enhancements/drift-detection.md new file mode 100644 index 000000000..8c2196907 --- /dev/null +++ b/enhancements/drift-detection.md @@ -0,0 +1,277 @@ +# Enhancement: Drift Detection and Automatic Reconciliation + +| Field | Value | +|-------|-------| +| **Status** | implementable | +| **Author(s)** | @eshulman | +| **Created** | 2026-02-03 | +| **Last Updated** | 2026-02-03 | +| **Tracking Issue** | TBD | + +## Summary + +This enhancement introduces drift detection and automatic reconciliation for ORC managed resources. The feature enables ORC to periodically check OpenStack resources for changes made outside of ORC (via CLI, dashboard, or other tools) and automatically restore them to match the desired state defined in the Kubernetes specification. + +Additionally, managed resources that are deleted externally from OpenStack will be automatically recreated by ORC, ensuring the declared state is maintained. + +## Motivation + +In production environments, OpenStack resources may be modified outside of ORC through various means: + +- Direct OpenStack CLI/SDK operations +- OpenStack Horizon dashboard +- Other automation tools or controllers +- Manual emergency interventions +- Third-party integrations + +Without drift detection, these changes go unnoticed until they cause issues, leading to configuration drift between the declared Kubernetes state and the actual OpenStack state. This undermines the declarative model that ORC provides. + +Similar Kubernetes controllers for cloud resources have implemented drift detection: + +- **AWS Controllers for Kubernetes (ACK)**: Drift detection is **enabled by default** with a 10-hour resync period. Uses a detect-then-correct approach: periodically describes the AWS resource and only updates if drift is found. Configuration is set per-controller by authors, not configurable per-resource by users. No per-resource opt-out mechanism documented. ([ACK Drift Recovery docs](https://aws-controllers-k8s.github.io/community/docs/user-docs/drift-recovery/)) + +- **Azure Service Operator (ASO)**: Drift detection is **enabled by default** with a 1-hour resync period. Uses a PUT-on-every-reconcile approach rather than detect-then-correct. Provides **per-resource opt-out** via `reconcile-policy` annotation for adopted resources users don't want fully managed. **Global configuration** via `AZURE_SYNC_PERIOD` environment variable. Rate limiting via token-bucket algorithm and `MAX_CONCURRENT_RECONCILES` for parallelism control. ([ASO Controller Settings](https://azure.github.io/azure-service-operator/guide/aso-controller-settings-options/), [ASO Change Detection ADR](https://azure.github.io/azure-service-operator/design/adr-2022-11-change-detection/)) + +**Key design observations:** +- Both projects enable drift detection by default +- ASO provides more user-facing configuration options (global and per-resource) +- Neither project documents behavior for externally-deleted resources + +## Goals + +- **Ensure state consistency**: Managed resources in OpenStack should match the desired state declared in Kubernetes +- **Detect external modifications**: Identify when OpenStack resources are modified outside of ORC +- **Automatic correction**: Restore drifted resources to their desired state without manual intervention +- **Resource recreation**: Recreate managed resources that are deleted externally from OpenStack +- **Configurable frequency**: Allow operators to tune the resync interval based on their requirements +- **Hierarchical configuration**: Support configuration at ORC-wide and per-resource levels, at minimum +- **Minimal API impact**: Avoid excessive OpenStack API calls that could trigger rate limiting + +## Non-Goals + +- **Real-time drift detection**: Event-driven detection of changes (would require OpenStack webhooks or very short polling intervals) +- **Drift reporting without correction**: Alerting on drift without taking corrective action. This applies to both mutable fields (which are corrected, not just reported) and immutable fields (which are ignored, not reported). May be considered as a future enhancement. +- **Selective field reconciliation**: Allowing some fields to drift while correcting others +- **Conflict resolution with merge semantics**: Merging external changes with desired state +- **Drift correction for unmanaged resources**: Unmanaged resources are not modified by ORC; however, periodic resync will refresh their status to reflect the current OpenStack state + +## Proposal + +### Periodic Resync Mechanism + +The drift detection mechanism works by periodically triggering reconciliation of resources. Unlike event-driven reconciles (triggered by Kubernetes spec/status changes), drift detection uses a time-based trigger to catch changes made directly in OpenStack. For managed resources, this includes drift correction; for unmanaged resources, this refreshes the status only. + +1. **Trigger**: After a resource reaches a stable state (Progressing=False), ORC schedules a resync after `resyncPeriod` duration +2. **Fetch**: On resync, ORC fetches the current state of the OpenStack resource +3. **Compare**: The current state is compared against the desired state in the Kubernetes spec +4. **Update**: If drift is detected, ORC updates the OpenStack resource to match the desired state +5. **Reschedule**: After successful reconciliation, the next resync is scheduled + +#### Implementation Details + +At the end of a successful reconciliation (when no other reschedule is pending), the controller schedules the next resync: + +```go +// If periodic resync is enabled and we're not already rescheduling for +// another reason, schedule the next resync to detect drift. +if resyncPeriod > 0 { + needsReschedule, _ := reconcileStatus.NeedsReschedule() + if !needsReschedule { + reconcileStatus = reconcileStatus.WithRequeue(resyncPeriod) + } +} +``` + +This ensures the controller automatically triggers reconciliation after the configured period. + +Additionally, `shouldReconcile` must be updated to allow periodic resync. Currently it returns `false` when `Progressing=False` and generation is current, which would discard resync requests. The updated logic checks the last sync timestamp: + +```go +func shouldReconcile(obj orcv1alpha1.ObjectWithConditions, resyncPeriod time.Duration) bool { + // ... existing checks ... + + // At this point, Progressing is False and generation is up to date. + // For periodic resync, check if enough time has passed since the last sync. + if resyncPeriod > 0 { + if lastSync := obj.GetLastSyncTime(); lastSync != nil { + return time.Since(lastSync.Time) >= resyncPeriod + } + return true // First sync after feature enablement + } + return false +} +``` + +**Note**: Using `Progressing.LastTransitionTime` is not suitable because it only updates when the condition value changes, not on every reconcile. A dedicated `LastSyncTime` status field is required (see Status Changes below). + +**Resources in terminal error are not resynced**: When a resource is in a terminal error state (e.g., invalid configuration, unrecoverable OpenStack error), periodic resync is not scheduled. Terminal errors indicate issues that cannot be resolved through automatic retry and require manual intervention to fix the underlying problem. This prevents wasted reconciliation cycles on resources that are known to be in an unrecoverable state. + +### API Changes + +A `resyncPeriod` field is added at the spec level, making it available to both managed and unmanaged resources: + +```yaml +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: critical-network +spec: + cloudCredentialsRef: + secretName: openstack-clouds + cloudName: openstack + managementPolicy: managed + resyncPeriod: 1h # Periodic resync every hour + resource: + description: Critical application network +``` + +**Default**: Disabled (`0`). Set a positive duration like `10h` to enable. + +### Status Changes + +A new `lastSyncTime` field is added to the status of all ORC resources: + +```yaml +status: + lastSyncTime: "2026-02-03T10:30:00Z" # Last successful reconciliation with OpenStack + id: "abc123" + # ... other status fields +``` + +This field is updated at the end of every successful reconciliation that fetches the resource from OpenStack. It is required because: + +1. **Controller restarts**: Without persisted state, the controller would lose track of when resources were last synced, potentially causing a thundering herd of reconciliations on restart. +2. **Accurate timing**: The `Progressing.LastTransitionTime` only updates when the condition value changes, not on every reconcile, making it unsuitable for tracking sync intervals. + +The `shouldReconcile` function uses this field to determine if enough time has passed since the last sync to trigger a periodic resync. + +### Behavior by Management Policy + +The periodic resync behavior differs based on `managementPolicy`: + +| Policy | On Resync | +|--------|-----------| +| `managed` | Fetch from OpenStack → correct drift → update status | +| `unmanaged` | Fetch from OpenStack → update status only (no writes to OpenStack) | + +This allows unmanaged/imported resources to keep their `status.resource` in sync with the actual OpenStack state without ORC modifying the resource. + +### Configuration Hierarchy + +Drift detection supports a two-level configuration hierarchy: + +| Level | Scope | Configuration Location | Precedence | +|-------|-------|----------------------|------------| +| ORC-wide | All resources across all types | CLI flag | Lowest | +| Per-resource | Individual resource instance | `spec.resyncPeriod` on the CR | Highest | + +**Resolution order**: Per-resource → ORC-wide → Built-in default (disabled) + +#### ORC-wide Configuration Options + +A CLI flag sets the global default: + +``` +--default-resync-period=10h +``` + +For per-resource-type configuration, platform teams can use [kro (Kube Resource Orchestrator)](https://kro.run/) to wrap ORC resources with organizational defaults without changes to ORC itself. + +### Resource Recreation on External Deletion + +When a resource with `managementPolicy=managed` is deleted from OpenStack but the ORC object still exists: + +1. On the next reconciliation, ORC attempts to fetch the resource by the ID stored in `status.id` +2. If not found and the resource was originally created by ORC (not imported), ORC recreates it +3. The new resource ID is stored in `status.id` + +#### Implementation Changes + +Currently, `GetOrCreateOSResource` returns a terminal error when fetching a resource by `status.id` results in a 404. To support resource recreation, this logic must be updated to: + +1. Check if `managementPolicy == managed` and the resource was not imported (no `importID` or `importFilter`) +2. If both conditions are met, clear `status.id` and proceed to the creation path instead of returning an error +3. If the resource was imported or is unmanaged, retain the existing terminal error behavior + +This ensures that managed resources created by ORC are automatically recreated, while imported or unmanaged resources correctly fail with a terminal error when deleted externally. + +**Behavior when drift detection is disabled** (`resyncPeriod: 0`): Periodic resyncs do not occur, so discovery of external deletion depends on other triggers (spec change, controller restart). When discovered, ORC will still recreate managed resources (not a terminal error). The difference is timing of discovery, not the recreation behavior itself. + +For **imported resources** that are deleted externally, this is always a terminal error regardless of drift detection settings, because the resource was not created by ORC and recreating it would not restore the original resource. + +**Note on dependent resources**: OpenStack enforces referential integrity for most resources (e.g., Networks cannot be deleted while Subnets exist). If resources are deleted through means that bypass these checks (direct database manipulation, OpenStack bugs), drift detection preserves ORC's existing reconciliation behavior: + +- **Parent resource (e.g., Network)**: On next reconciliation, `GetOSResourceByID` returns 404 → terminal error ("resource has been deleted from OpenStack"). +- **Dependent resource update path (e.g., Subnet update)**: The controller doesn't check if its parent dependency is in terminal error. It fetches the resource by `status.id`, and if successful, proceeds with the update. The result depends on what OpenStack returns for that specific operation and would preserve the existing error handling behavior. +- **Dependent resource create/recreate path**: The controller checks `IsAvailable(parent)` before proceeding. If the parent is in terminal error, the dependent waits on the dependency (not terminal, just waiting). + +These behaviors exist regardless of drift detection—drift detection only changes scheduling, not reconciliation logic. Resolving such inconsistencies requires manual intervention. + +### Field Coverage + +Drift detection covers all **mutable fields** that ORC actuators implement update operations for. Before this feature is considered stable, all actuator implementations must be audited to ensure they cover all mutable fields. + +## Risks and Edge Cases + +### Split-Brain Scenarios + +**Risk**: Multiple controllers or systems may be managing the same OpenStack resources, leading to conflicts where changes are repeatedly overwritten. + +**Mitigation**: +- Document that ORC should be the sole manager of resources it creates +- Report conflicts in resource conditions for observability + +### API Rate Limiting + +**Risk**: Frequent resync across many resources could trigger OpenStack API rate limiting. + +**Mitigation**: +- Disabled by default; when enabled, recommend conservative intervals (e.g., 10 hours) +- Add random jitter to resync times to avoid thundering herd: since reconciliation already uses "requeue after X duration", jitter simply adds a random offset (e.g., ±10%) to the resync period, spreading resyncs over time rather than having them fire simultaneously +- Allow operators to disable or lengthen resync for stable resources + +### Controller Resource Consumption + +**Risk**: Frequent reconciliation increases CPU and memory usage on the ORC controller. + +**Mitigation**: +- Disabled by default; when enabled, conservative intervals limit reconciliation frequency + +### Conflicts with External Systems + +**Risk**: If resources are intentionally managed by external systems (e.g., autoscalers, other controllers), drift correction can cause unexpected behavior. + +**Mitigation**: +- Allow `resyncPeriod: 0` to disable drift detection +- Use `managementPolicy: unmanaged` for externally managed resources +- Document the implications clearly in the user guide + +### Upgrade/Downgrade Considerations + +**Risk**: Users upgrading to a version with drift detection may experience unexpected reconciliations. + +**Mitigation**: Drift detection is disabled by default (opt-in), so users upgrading will not experience any behavior change unless they explicitly enable it. Document the new feature in release notes. + +## Alternatives Considered + +### Event-Driven Drift Detection + +Use OpenStack notifications (Oslo messaging) to detect changes in real-time. + +**Rejected because**: Requires OpenStack notification infrastructure, complex to implement, not all deployments have notifications enabled. + +### Drift Detection Without Correction + +Detect and report drift without automatically correcting it. + +**Out of scope for this enhancement**: While drift notification has value for observability, it is better addressed as a separate alerting effort. This enhancement focuses on drift correction; reporting-only mode could be added as a future management policy option. + +### Watch-Based Detection + +Implement a watcher that periodically lists all resources from OpenStack and compares. + +**Rejected because**: List operations can be expensive, harder to implement with proper filtering, and per-resource reconciliation integrates naturally with controller-runtime. + +## Implementation History + +- 2026-02-03: Enhancement proposed From 5eb9010c559a14109319a1062dda229d778bd548 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Wed, 4 Feb 2026 17:09:55 +0100 Subject: [PATCH 139/237] ApplicationCredential Actuator and Status On-behalf-of: SAP nils.gondermann@sap.com --- .../applicationcredential/actuator.go | 234 ++++++++++-------- .../applicationcredential/actuator_test.go | 119 --------- .../applicationcredential/controller.go | 90 ++++++- .../applicationcredential/status.go | 32 ++- internal/osclients/applicationcredential.go | 33 +-- 5 files changed, 262 insertions(+), 246 deletions(-) delete mode 100644 internal/controllers/applicationcredential/actuator_test.go diff --git a/internal/controllers/applicationcredential/actuator.go b/internal/controllers/applicationcredential/actuator.go index 1b3ad3efc..6e1c30e0c 100644 --- a/internal/controllers/applicationcredential/actuator.go +++ b/internal/controllers/applicationcredential/actuator.go @@ -18,6 +18,7 @@ package applicationcredential import ( "context" + "fmt" "iter" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials" @@ -29,7 +30,6 @@ import ( 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" @@ -41,7 +41,6 @@ type ( 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] ) @@ -71,27 +70,38 @@ func (actuator applicationcredentialActuator) ListOSResourcesForAdoption(ctx con 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 + user, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, + &resourceSpec.UserRef, "User", + func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + + if user.Status.ID == nil { + return nil, false + } + + var filters []osclients.ResourceFilter[osResourceT] + + // Add client-side filters + if resourceSpec.Description != nil { + filters = append(filters, func(f *applicationcredentials.ApplicationCredential) bool { + return f.Description == *resourceSpec.Description + }) + } listOpts := applicationcredentials.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Name: getResourceName(orcObject), } - return actuator.osClient.ListApplicationCredentials(ctx, listOpts), true + return actuator.listOSResources(ctx, ptr.Deref(user.Status.ID, ""), filters, listOpts), true } func (actuator applicationcredentialActuator) 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 user, rs := dependency.FetchDependency( ctx, actuator.k8sClient, obj.Namespace, - filter.UserRef, "User", + &filter.UserRef, "User", func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -100,14 +110,25 @@ func (actuator applicationcredentialActuator) ListOSResourcesForImport(ctx conte return nil, reconcileStatus } + var filters []osclients.ResourceFilter[osResourceT] + + // Add client-side filters + if filter.Description != nil { + filters = append(filters, func(f *applicationcredentials.ApplicationCredential) bool { + return f.Description == *filter.Description + }) + } + listOpts := applicationcredentials.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - UserID: ptr.Deref(user.Status.ID, ""), - // TODO(scaffolding): Add more import filters + Name: string(ptr.Deref(filter.Name, "")), } - return actuator.osClient.ListApplicationCredentials(ctx, listOpts), reconcileStatus + return actuator.listOSResources(ctx, ptr.Deref(user.Status.ID, ""), filters, listOpts), nil +} + +func (actuator applicationcredentialActuator) listOSResources(ctx context.Context, userID string, filters []osclients.ResourceFilter[osResourceT], listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { + applicationCredentials := actuator.osClient.ListApplicationCredentials(ctx, userID, listOpts) + return osclients.Filter(applicationCredentials, filters...) } func (actuator applicationcredentialActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -118,116 +139,129 @@ func (actuator applicationcredentialActuator) CreateResource(ctx context.Context return nil, progress.WrapError( orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) } + var reconcileStatus progress.ReconcileStatus - var userID string - user, userDepRS := userDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS) - if user != nil { - userID = ptr.Deref(user.Status.ID, "") - } - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { - return nil, reconcileStatus - } - createOpts := applicationcredentials.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - UserID: userID, - // TODO(scaffolding): Add more fields - } + user, userDepRS := userDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) - osResource, err := actuator.osClient.CreateApplicationCredential(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) + rolesMap, roleDepRs := roleDependency.GetDependencies( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + + serviceMap, serviceDepRS := serviceDependency.GetDependencies( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + + secret, secretReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + &resource.SecretRef, "Secret", + func(*corev1.Secret) bool { return true }, // Secrets don't have availability status + ) + + var secretData []byte + if secretReconcileStatus == nil { + var ok bool + secretData, ok = secret.Data["value"] + if !ok { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.NewReconcileStatus().WithProgressMessage("Application credential secret does not contain \"value\" key")) } - return nil, progress.WrapError(err) } - return osResource, nil -} + reconcileStatus = reconcileStatus. + WithReconcileStatus(userDepRS). + WithReconcileStatus(roleDepRs). + WithReconcileStatus(serviceDepRS). + WithReconcileStatus(secretReconcileStatus) -func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, resource.ID)) -} - -func (actuator applicationcredentialActuator) 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")) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus } - updateOpts := applicationcredentials.UpdateOpts{} + roleList := make([]applicationcredentials.Role, len(resource.RoleRefs)) + for i := range resource.RoleRefs { + roleName := string(resource.RoleRefs[i]) + role, ok := rolesMap[roleName] + if !ok { + // Programming error + return nil, progress.WrapError(fmt.Errorf("role %s was not returned by GetDependencies", roleName)) + } + roleList[i].ID = *role.Status.ID + } - handleNameUpdate(&updateOpts, obj, osResource) - handleDescriptionUpdate(&updateOpts, resource, osResource) + accessRuleList := make([]applicationcredentials.AccessRule, len(resource.AccessRules)) + for i := range resource.AccessRules { + accessRuleSpec := &resource.AccessRules[i] + accessRule := &accessRuleList[i] + + if accessRuleSpec.ServiceRef != nil { + serviceName := string(*accessRuleSpec.ServiceRef) + service, ok := serviceMap[serviceName] + if !ok { + // Programming error + return nil, progress.WrapError(fmt.Errorf("service %s was not returned by GetDependencies", serviceName)) + } + accessRule.Service = service.Status.Resource.Type + } - // TODO(scaffolding): add handler for all fields supporting mutability + if accessRuleSpec.Path != nil { + accessRule.Path = *accessRuleSpec.Path + } - 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 + if accessRuleSpec.Method != nil { + accessRule.Method = string(*accessRuleSpec.Method) + } } - _, err = actuator.osClient.UpdateApplicationCredential(ctx, osResource.ID, updateOpts) + createOpts := applicationcredentials.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + Unrestricted: ptr.Deref(resource.Unrestricted, false), + Secret: string(secretData), + Roles: roleList, + AccessRules: accessRuleList, + } - // 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 resource.ExpiresAt != nil { + createOpts.ExpiresAt = &resource.ExpiresAt.Time } + osResource, err := actuator.osClient.CreateApplicationCredential(ctx, ptr.Deref(user.Status.ID, ""), createOpts) if err != nil { - return progress.WrapError(err) + // 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 progress.NeedsRefresh() + return osResource, nil } -func needsUpdate(updateOpts applicationcredentials.UpdateOpts) (bool, error) { - updateOptsMap, err := updateOpts.ToApplicationCredentialUpdateMap() - if err != nil { - return false, err - } - - updateMap, ok := updateOptsMap["application_credentials"].(map[string]any) - if !ok { - updateMap = make(map[string]any) - } +func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context, orcObject orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + var reconcileStatus progress.ReconcileStatus - return len(updateMap) > 0, nil -} + user, userDepRS := userDependency.GetDependency( + ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) -func handleNameUpdate(updateOpts *applicationcredentials.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { - name := getResourceName(obj) - if osResource.Name != name { - updateOpts.Name = &name - } -} + reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS) -func handleDescriptionUpdate(updateOpts *applicationcredentials.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - description := ptr.Deref(resource.Description, "") - if osResource.Description != description { - updateOpts.Description = &description + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return reconcileStatus } -} -func (actuator applicationcredentialActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { - return []resourceReconciler{ - actuator.updateResource, - }, nil + return progress.WrapError(actuator.osClient.DeleteApplicationCredential(ctx, ptr.Deref(user.Status.ID, ""), resource.ID)) } type applicationcredentialHelperFactory struct{} diff --git a/internal/controllers/applicationcredential/actuator_test.go b/internal/controllers/applicationcredential/actuator_test.go deleted file mode 100644 index 1a8015f89..000000000 --- a/internal/controllers/applicationcredential/actuator_test.go +++ /dev/null @@ -1,119 +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 applicationcredential - -import ( - "testing" - - "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/applicationcredentials" - 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 applicationcredentials.UpdateOpts - expectChange bool - }{ - { - name: "Empty base opts", - updateOpts: applicationcredentials.UpdateOpts{}, - expectChange: false, - }, - { - name: "Updated opts", - updateOpts: applicationcredentials.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.ApplicationCredential{} - resource.Name = "object-name" - resource.Spec = orcv1alpha1.ApplicationCredentialSpec{ - Resource: &orcv1alpha1.ApplicationCredentialResourceSpec{Name: tt.newValue}, - } - osResource := &osResourceT{Name: tt.existingValue} - - updateOpts := applicationcredentials.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.ApplicationCredentialResourceSpec{Description: tt.newValue} - osResource := &osResourceT{Description: tt.existingValue} - - updateOpts := applicationcredentials.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/applicationcredential/controller.go b/internal/controllers/applicationcredential/controller.go index 34a86a51e..4e41a989f 100644 --- a/internal/controllers/applicationcredential/controller.go +++ b/internal/controllers/applicationcredential/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" @@ -39,6 +40,59 @@ const controllerName = "applicationcredential" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=applicationcredentials,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=applicationcredentials/status,verbs=get;update;patch +var ( + // We don't need a deletion guard on the application credential secret because it's only + // used on creation. + secretDependency = dependency.NewDependency[*orcv1alpha1.ApplicationCredentialList, *corev1.Secret]( + "spec.resource.secretRef", + func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { + resource := applicationcredential.Spec.Resource + if resource == nil { + return nil + } + + return []string{string(resource.SecretRef)} + }, + ) + + roleDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ApplicationCredentialList, *orcv1alpha1.Role]( + "spec.resource.roleRefs", + func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { + resource := applicationcredential.Spec.Resource + if resource == nil { + return nil + } + + roles := make([]string, len(resource.RoleRefs)) + for i := range resource.RoleRefs { + roles[i] = string(resource.RoleRefs[i]) + } + return roles + }, + finalizer, externalObjectFieldOwner, + ) + + serviceDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ApplicationCredentialList, *orcv1alpha1.Service]( + "spec.resource.accessRules[].serviceRef", + func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { + resource := applicationcredential.Spec.Resource + if resource == nil { + return nil + } + + services := make([]string, 0) + for i := range resource.AccessRules { + if resource.AccessRules[i].ServiceRef == nil { + continue + } + services = append(services, string(*resource.AccessRules[i].ServiceRef)) + } + return services + }, + finalizer, externalObjectFieldOwner, + ) +) + type applicationcredentialReconcilerConstructor struct { scopeFactory scope.Factory } @@ -67,10 +121,10 @@ var userImportDependency = dependency.NewDependency[*orcv1alpha1.ApplicationCred "spec.import.filter.userRef", func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { resource := applicationcredential.Spec.Import - if resource == nil || resource.Filter == nil || resource.Filter.UserRef == nil { + if resource == nil || resource.Filter == nil { return nil } - return []string{string(*resource.Filter.UserRef)} + return []string{string(resource.Filter.UserRef)} }, ) @@ -89,6 +143,21 @@ func (c applicationcredentialReconcilerConstructor) SetupWithManager(ctx context return err } + secretWatchEventHandler, err := secretDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + roleWatchEventHandler, err := roleDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + serviceWatchEventHandler, err := serviceDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). Watches(&orcv1alpha1.User{}, userWatchEventHandler, @@ -98,12 +167,29 @@ func (c applicationcredentialReconcilerConstructor) SetupWithManager(ctx context Watches(&orcv1alpha1.User{}, userImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &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 only secrets. + Watches(&corev1.Secret{}, secretWatchEventHandler). + Watches(&orcv1alpha1.Role{}, roleWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Role{})), + ). + Watches(&orcv1alpha1.Service{}, serviceWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Service{})), + ). For(&orcv1alpha1.ApplicationCredential{}) if err := errors.Join( userDependency.AddToManager(ctx, mgr), userImportDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), + secretDependency.AddToManager(ctx, mgr), + roleDependency.AddToManager(ctx, mgr), + serviceDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), ); err != nil { return err diff --git a/internal/controllers/applicationcredential/status.go b/internal/controllers/applicationcredential/status.go index 34141fd33..2530a65e9 100644 --- a/internal/controllers/applicationcredential/status.go +++ b/internal/controllers/applicationcredential/status.go @@ -50,15 +50,39 @@ func (applicationcredentialStatusWriter) ResourceAvailableStatus(orcObject *orcv func (applicationcredentialStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { resourceStatus := orcapplyconfigv1alpha1.ApplicationCredentialResourceStatus(). - WithUserID(osResource.UserID). - WithName(osResource.Name) + WithName(osResource.Name). + WithUnrestricted(osResource.Unrestricted). + WithProjectID(osResource.ProjectID) - // TODO(scaffolding): add all of the fields supported in the ApplicationCredentialResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + if !osResource.ExpiresAt.IsZero() { + resourceStatus.WithExpiresAt(metav1.NewTime(osResource.ExpiresAt)) + } if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } + for i := range osResource.Roles { + roleStatus := orcapplyconfigv1alpha1.ApplicationCredentialRoleStatus(). + WithID(osResource.Roles[i].ID). + WithName(osResource.Roles[i].Name) + + if osResource.Roles[i].DomainID != "" { + roleStatus.WithDomainID(osResource.Roles[i].DomainID) + } + + resourceStatus.WithRoles(roleStatus) + } + + for i := range osResource.AccessRules { + accessRuleStatus := orcapplyconfigv1alpha1.ApplicationCredentialAccessRuleStatus(). + WithID(osResource.AccessRules[i].ID). + WithPath(osResource.AccessRules[i].Path). + WithMethod(osResource.AccessRules[i].Method). + WithService(osResource.AccessRules[i].Service) + + resourceStatus.WithAccessRules(accessRuleStatus) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/osclients/applicationcredential.go b/internal/osclients/applicationcredential.go index 2742093fa..30c63adad 100644 --- a/internal/osclients/applicationcredential.go +++ b/internal/osclients/applicationcredential.go @@ -32,11 +32,10 @@ import ( ) type ApplicationCredentialClient interface { - ListApplicationCredentials(ctx context.Context, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] - CreateApplicationCredential(ctx context.Context, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) - DeleteApplicationCredential(ctx context.Context, resourceID string) error + ListApplicationCredentials(ctx context.Context, userID string, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] + CreateApplicationCredential(ctx context.Context, userID string, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) + DeleteApplicationCredential(ctx context.Context, userID string, resourceID string) error GetApplicationCredential(ctx context.Context, resourceID string) (*applicationcredentials.ApplicationCredential, error) - UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) } type applicationcredentialClient struct{ client *gophercloud.ServiceClient } @@ -55,23 +54,19 @@ func NewApplicationCredentialClient(providerClient *gophercloud.ProviderClient, return &applicationcredentialClient{client}, nil } -func (c applicationcredentialClient) ListApplicationCredentials(ctx context.Context, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { - pager := applicationcredentials.List(c.client, listOpts) +func (c applicationcredentialClient) ListApplicationCredentials(ctx context.Context, userID string, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { + pager := applicationcredentials.List(c.client, userID, listOpts) return func(yield func(*applicationcredentials.ApplicationCredential, error) bool) { _ = pager.EachPage(ctx, yieldPage(applicationcredentials.ExtractApplicationCredentials, yield)) } } -func (c applicationcredentialClient) CreateApplicationCredential(ctx context.Context, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { - return applicationcredentials.Create(ctx, c.client, opts).Extract() +func (c applicationcredentialClient) CreateApplicationCredential(ctx context.Context, userID string, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { + return applicationcredentials.Create(ctx, c.client, userID, opts).Extract() } -func (c applicationcredentialClient) DeleteApplicationCredential(ctx context.Context, resourceID string) error { - return applicationcredentials.Delete(ctx, c.client, resourceID).ExtractErr() -} - -func (c applicationcredentialClient) UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { - return applicationcredentials.Update(ctx, c.client, id, opts).Extract() +func (c applicationcredentialClient) DeleteApplicationCredential(ctx context.Context, userID string, resourceID string) error { + return applicationcredentials.Delete(ctx, c.client, userID, resourceID).ExtractErr() } func (c applicationcredentialClient) GetApplicationCredential(ctx context.Context, resourceID string) (*applicationcredentials.ApplicationCredential, error) { @@ -142,24 +137,20 @@ func NewApplicationCredentialErrorClient(e error) ApplicationCredentialClient { return applicationcredentialErrorClient{e} } -func (e applicationcredentialErrorClient) ListApplicationCredentials(_ context.Context, _ applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { +func (e applicationcredentialErrorClient) ListApplicationCredentials(_ context.Context, _ string, _ applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { return func(yield func(*applicationcredentials.ApplicationCredential, error) bool) { yield(nil, e.error) } } -func (e applicationcredentialErrorClient) CreateApplicationCredential(_ context.Context, _ applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { +func (e applicationcredentialErrorClient) CreateApplicationCredential(_ context.Context, _ string, _ applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { return nil, e.error } -func (e applicationcredentialErrorClient) DeleteApplicationCredential(_ context.Context, _ string) error { +func (e applicationcredentialErrorClient) DeleteApplicationCredential(_ context.Context, _ string, _ string) error { return e.error } func (e applicationcredentialErrorClient) GetApplicationCredential(_ context.Context, _ string) (*applicationcredentials.ApplicationCredential, error) { return nil, e.error } - -func (e applicationcredentialErrorClient) UpdateApplicationCredential(_ context.Context, _ string, _ applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { - return nil, e.error -} From cf0e040154da35ba311c55528c79827376d8f605 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Tue, 17 Mar 2026 12:17:59 +0100 Subject: [PATCH 140/237] ApplicationCredential Regenerate Interfaces On-behalf-of: SAP nils.gondermann@sap.com --- api/v1alpha1/zz_generated.deepcopy.go | 141 +++- cmd/models-schema/zz_generated.openapi.go | 717 ++++++++++++------ ...ck.k-orc.cloud_applicationcredentials.yaml | 147 +++- .../osclients/mock/applicationcredential.go | 39 +- .../applicationcredentialaccessrule.go | 61 ++ .../applicationcredentialaccessrulestatus.go | 66 ++ .../v1alpha1/applicationcredentialfilter.go | 18 +- .../applicationcredentialresourcespec.go | 59 +- .../applicationcredentialresourcestatus.go | 64 +- .../applicationcredentialrolestatus.go | 57 ++ .../applyconfiguration/internal/internal.go | 80 +- pkg/clients/applyconfiguration/utils.go | 6 + website/docs/crd-reference.md | 137 +++- 13 files changed, 1260 insertions(+), 332 deletions(-) create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrule.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrulestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialrolestatus.go diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9ad226cf9..bf527d847 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -364,6 +364,71 @@ func (in *ApplicationCredential) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialAccessRule) DeepCopyInto(out *ApplicationCredentialAccessRule) { + *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } + if in.Method != nil { + in, out := &in.Method, &out.Method + *out = new(HTTPMethod) + **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 ApplicationCredentialAccessRule. +func (in *ApplicationCredentialAccessRule) DeepCopy() *ApplicationCredentialAccessRule { + if in == nil { + return nil + } + out := new(ApplicationCredentialAccessRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialAccessRuleStatus) DeepCopyInto(out *ApplicationCredentialAccessRuleStatus) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } + if in.Method != nil { + in, out := &in.Method, &out.Method + *out = new(string) + **out = **in + } + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialAccessRuleStatus. +func (in *ApplicationCredentialAccessRuleStatus) DeepCopy() *ApplicationCredentialAccessRuleStatus { + if in == nil { + return nil + } + out := new(ApplicationCredentialAccessRuleStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationCredentialFilter) DeepCopyInto(out *ApplicationCredentialFilter) { *out = *in @@ -377,11 +442,6 @@ func (in *ApplicationCredentialFilter) DeepCopyInto(out *ApplicationCredentialFi *out = new(string) **out = **in } - if in.UserRef != nil { - in, out := &in.UserRef, &out.UserRef - *out = new(KubernetesNameRef) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialFilter. @@ -464,6 +524,27 @@ func (in *ApplicationCredentialResourceSpec) DeepCopyInto(out *ApplicationCreden *out = new(string) **out = **in } + if in.Unrestricted != nil { + in, out := &in.Unrestricted, &out.Unrestricted + *out = new(bool) + **out = **in + } + if in.RoleRefs != nil { + in, out := &in.RoleRefs, &out.RoleRefs + *out = make([]KubernetesNameRef, len(*in)) + copy(*out, *in) + } + if in.AccessRules != nil { + in, out := &in.AccessRules, &out.AccessRules + *out = make([]ApplicationCredentialAccessRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExpiresAt != nil { + in, out := &in.ExpiresAt, &out.ExpiresAt + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialResourceSpec. @@ -479,6 +560,24 @@ func (in *ApplicationCredentialResourceSpec) DeepCopy() *ApplicationCredentialRe // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationCredentialResourceStatus) DeepCopyInto(out *ApplicationCredentialResourceStatus) { *out = *in + if in.Roles != nil { + in, out := &in.Roles, &out.Roles + *out = make([]ApplicationCredentialRoleStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ExpiresAt != nil { + in, out := &in.ExpiresAt, &out.ExpiresAt + *out = (*in).DeepCopy() + } + if in.AccessRules != nil { + in, out := &in.AccessRules, &out.AccessRules + *out = make([]ApplicationCredentialAccessRuleStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialResourceStatus. @@ -491,6 +590,36 @@ func (in *ApplicationCredentialResourceStatus) DeepCopy() *ApplicationCredential return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationCredentialRoleStatus) DeepCopyInto(out *ApplicationCredentialRoleStatus) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.DomainID != nil { + in, out := &in.DomainID, &out.DomainID + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialRoleStatus. +func (in *ApplicationCredentialRoleStatus) DeepCopy() *ApplicationCredentialRoleStatus { + if in == nil { + return nil + } + out := new(ApplicationCredentialRoleStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationCredentialSpec) DeepCopyInto(out *ApplicationCredentialSpec) { *out = *in @@ -540,7 +669,7 @@ func (in *ApplicationCredentialStatus) DeepCopyInto(out *ApplicationCredentialSt if in.Resource != nil { in, out := &in.Resource, &out.Resource *out = new(ApplicationCredentialResourceStatus) - **out = **in + (*in).DeepCopyInto(*out) } } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 637b3a0d8..bf0f5a82f 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -30,243 +30,246 @@ 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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredential": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredential(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialImport": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialList": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference": schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Domain": schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainFilter": schema_openstack_resource_controller_v2_api_v1alpha1_DomainFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainImport": schema_openstack_resource_controller_v2_api_v1alpha1_DomainImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainList": schema_openstack_resource_controller_v2_api_v1alpha1_DomainList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_DomainResourceSpec(ref), - "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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByNeutronTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByNeutronTags(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByServerTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByServerTags(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FixedIPStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Flavor": schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorList": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIP": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIP(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPImport": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPList": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Group": schema_openstack_resource_controller_v2_api_v1alpha1_Group(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_GroupFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_GroupImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupList": schema_openstack_resource_controller_v2_api_v1alpha1_GroupList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_GroupResourceSpec(ref), - "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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Image": schema_openstack_resource_controller_v2_api_v1alpha1_Image(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContent": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContent(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContentSourceDownload": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContentSourceDownload(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ImageFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageHash": schema_openstack_resource_controller_v2_api_v1alpha1_ImageHash(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageImport": schema_openstack_resource_controller_v2_api_v1alpha1_ImageImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageList": schema_openstack_resource_controller_v2_api_v1alpha1_ImageList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageProperties": schema_openstack_resource_controller_v2_api_v1alpha1_ImageProperties(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesHardware": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesHardware(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesOperatingSystem": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesOperatingSystem(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatusExtra": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatusExtra(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPair": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPair(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairFilter": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairImport": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairList": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions": schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Network": schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkImport": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkList": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NeutronStatusMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_NeutronStatusMetadata(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Port": schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortFilter": schema_openstack_resource_controller_v2_api_v1alpha1_PortFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortImport": schema_openstack_resource_controller_v2_api_v1alpha1_PortImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortList": schema_openstack_resource_controller_v2_api_v1alpha1_PortList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Project": schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectImport": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectList": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Router": schema_openstack_resource_controller_v2_api_v1alpha1_Router(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RouterFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterImport": schema_openstack_resource_controller_v2_api_v1alpha1_RouterImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterface": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterface(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroup": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroup(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRule": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRule(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRuleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRuleStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Server": schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroup": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRules": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRules(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRulesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRulesStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerImport(ref), - "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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Service": schema_openstack_resource_controller_v2_api_v1alpha1_Service(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceList": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Subnet": schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), - "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.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), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadata(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadataStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeType": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeType(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpecStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpecStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeFilter(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeImport(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeList(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceStatus(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeSpec(ref), - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeStatus(ref), - "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredential": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredential(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialAccessRule": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialAccessRule(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialAccessRuleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialAccessRuleStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialImport": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialList": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialRoleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialRoleStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference": schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Domain": schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainFilter": schema_openstack_resource_controller_v2_api_v1alpha1_DomainFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainImport": schema_openstack_resource_controller_v2_api_v1alpha1_DomainImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainList": schema_openstack_resource_controller_v2_api_v1alpha1_DomainList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_DomainResourceSpec(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByNeutronTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByNeutronTags(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByServerTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByServerTags(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FixedIPStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Flavor": schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorList": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIP": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIP(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPImport": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPList": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Group": schema_openstack_resource_controller_v2_api_v1alpha1_Group(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_GroupFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_GroupImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupList": schema_openstack_resource_controller_v2_api_v1alpha1_GroupList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_GroupResourceSpec(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Image": schema_openstack_resource_controller_v2_api_v1alpha1_Image(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContent": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContent(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageContentSourceDownload": schema_openstack_resource_controller_v2_api_v1alpha1_ImageContentSourceDownload(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ImageFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageHash": schema_openstack_resource_controller_v2_api_v1alpha1_ImageHash(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageImport": schema_openstack_resource_controller_v2_api_v1alpha1_ImageImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageList": schema_openstack_resource_controller_v2_api_v1alpha1_ImageList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageProperties": schema_openstack_resource_controller_v2_api_v1alpha1_ImageProperties(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesHardware": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesHardware(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImagePropertiesOperatingSystem": schema_openstack_resource_controller_v2_api_v1alpha1_ImagePropertiesOperatingSystem(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ImageSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageStatusExtra": schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatusExtra(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPair": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPair(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairFilter": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairImport": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairList": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairSpec": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions": schema_openstack_resource_controller_v2_api_v1alpha1_ManagedOptions(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Network": schema_openstack_resource_controller_v2_api_v1alpha1_Network(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkImport": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkList": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NeutronStatusMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_NeutronStatusMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Port": schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortFilter": schema_openstack_resource_controller_v2_api_v1alpha1_PortFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortImport": schema_openstack_resource_controller_v2_api_v1alpha1_PortImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortList": schema_openstack_resource_controller_v2_api_v1alpha1_PortList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortRangeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortRangeStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Project": schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectImport": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectList": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Router": schema_openstack_resource_controller_v2_api_v1alpha1_Router(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RouterFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterImport": schema_openstack_resource_controller_v2_api_v1alpha1_RouterImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterface": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterface(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterInterfaceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterList": schema_openstack_resource_controller_v2_api_v1alpha1_RouterList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RouterSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RouterStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroup": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroup(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRule": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRule(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupRuleStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupRuleStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Server": schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroup": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupList": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRules": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRules(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupRulesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupRulesStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServerImport(ref), + "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Service": schema_openstack_resource_controller_v2_api_v1alpha1_Service(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceImport": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceList": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Subnet": schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetImport": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetList": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref), + "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.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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeMetadataStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeType": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeType(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeExtraSpecStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeExtraSpecStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeFilter": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeImport": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeList": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeStatus": schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeStatus(ref), + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), "k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref), "k8s.io/api/core/v1.AppArmorProfile": schema_k8sio_api_core_v1_AppArmorProfile(ref), "k8s.io/api/core/v1.AttachedVolume": schema_k8sio_api_core_v1_AttachedVolume(ref), @@ -1097,27 +1100,87 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredential( } } -func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialAccessRule(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ApplicationCredentialFilter defines an existing resource by its properties", + Description: "ApplicationCredentialAccessRule defines an access rule", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "path": { SchemaProps: spec.SchemaProps{ - Description: "name of the existing resource", + Description: "path that the application credential is permitted to access", Type: []string{"string"}, Format: "", }, }, - "description": { + "method": { SchemaProps: spec.SchemaProps{ - Description: "description of the existing resource", + Description: "method that the application credential is permitted to use for a given API endpoint", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceRef": { + SchemaProps: spec.SchemaProps{ + Description: "serviceRef identifier for the service that the application credential is permitted to access", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialAccessRuleStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the ID of this access rule", Type: []string{"string"}, Format: "", }, }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path that the application credential is permitted to access", + Type: []string{"string"}, + Format: "", + }, + }, + "method": { + SchemaProps: spec.SchemaProps{ + Description: "method that the application credential is permitted to use for a given API endpoint", + Type: []string{"string"}, + Format: "", + }, + }, + "service": { + SchemaProps: spec.SchemaProps{ + Description: "service type identifier for the service that the application credential is permitted to access", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplicationCredentialFilter defines an existing resource by its properties", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ "userRef": { SchemaProps: spec.SchemaProps{ Description: "userRef is a reference to the ORC User which this resource is associated with.", @@ -1125,7 +1188,22 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialF Format: "", }, }, + "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: "", + }, + }, }, + Required: []string{"userRef"}, }, }, } @@ -1238,10 +1316,71 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialR Format: "", }, }, + "unrestricted": { + SchemaProps: spec.SchemaProps{ + Description: "unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is a reference to a Secret containing the application credential secret", + Type: []string{"string"}, + Format: "", + }, + }, + "roleRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "roleRefs may only contain roles that the user has assigned on the project. If not provided, the roles assigned to the application credential will be the same as the roles in the current token.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "accessRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "accessRules is a list of fine grained access control rules", + 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.ApplicationCredentialAccessRule"), + }, + }, + }, + }, + }, + "expiresAt": { + SchemaProps: spec.SchemaProps{ + Description: "expiresAt is the time of expiration for the application credential. If unset, the application credential does not expire.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, - Required: []string{"userRef"}, + Required: []string{"userRef", "secretRef"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialAccessRule", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -1266,9 +1405,95 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialR Format: "", }, }, - "userID": { + "unrestricted": { + SchemaProps: spec.SchemaProps{ + Description: "unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts", + Type: []string{"boolean"}, + Format: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID of the project the application credential was created for and that authentication requests using this application credential will be scoped to.", + Type: []string{"string"}, + Format: "", + }, + }, + "roles": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "roles is a list of role objects may only contain roles that the user has assigned on the project", + 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.ApplicationCredentialRoleStatus"), + }, + }, + }, + }, + }, + "expiresAt": { + SchemaProps: spec.SchemaProps{ + Description: "expiresAt is the time of expiration for the application credential. If unset, the application credential does not expire.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "accessRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "accessRules is a list of fine grained access control rules", + 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.ApplicationCredentialAccessRuleStatus"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialAccessRuleStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialRoleStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialRoleStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name of an existing role", + Type: []string{"string"}, + Format: "", + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the ID of a role", + Type: []string{"string"}, + Format: "", + }, + }, + "domainID": { SchemaProps: spec.SchemaProps{ - Description: "userID is the ID of the User to which the resource is associated.", + Description: "domainID of the domain of this role", Type: []string{"string"}, Format: "", }, diff --git a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml index 449790ab8..f16fe117f 100644 --- a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.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: applicationcredentials.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud @@ -89,12 +89,11 @@ spec: 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 + minProperties: 2 properties: description: description: description of the existing resource - maxLength: 255 - minLength: 1 + maxLength: 1024 type: string name: description: name of the existing resource @@ -108,6 +107,8 @@ spec: maxLength: 253 minLength: 1 type: string + required: + - userRef type: object id: description: |- @@ -156,12 +157,54 @@ spec: resource must be specified if the management policy is `managed`. properties: + accessRules: + description: accessRules is a list of fine grained access control + rules + items: + description: ApplicationCredentialAccessRule defines an access + rule + minProperties: 1 + properties: + method: + description: method that the application credential is permitted + to use for a given API endpoint + enum: + - CONNECT + - DELETE + - GET + - HEAD + - OPTIONS + - PATCH + - POST + - PUT + - TRACE + type: string + path: + description: path that the application credential is permitted + to access + maxLength: 1024 + type: string + serviceRef: + description: serviceRef identifier for the service that + the application credential is permitted to access + maxLength: 253 + minLength: 1 + type: string + type: object + maxItems: 256 + type: array + x-kubernetes-list-type: atomic description: description: description is a human-readable description for the resource. maxLength: 255 minLength: 1 type: string + expiresAt: + description: expiresAt is the time of expiration for the application + credential. If unset, the application credential does not expire. + format: date-time + type: string name: description: |- name will be the name of the created resource. If not specified, the @@ -170,18 +213,42 @@ spec: minLength: 1 pattern: ^[^,]+$ type: string + roleRefs: + description: roleRefs may only contain roles that the user has + assigned on the project. If not provided, the roles assigned + to the application credential will be the same as the roles + in the current token. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + secretRef: + description: secretRef is a reference to a Secret containing the + application credential secret + maxLength: 253 + minLength: 1 + type: string + unrestricted: + description: unrestricted is a flag indicating whether the application + credential may be used for creation or destruction of other + application credentials or trusts + type: boolean userRef: description: userRef is a reference to the ORC User which this resource is associated with. maxLength: 253 minLength: 1 type: string - x-kubernetes-validations: - - message: userRef is immutable - rule: self == oldSelf required: + - secretRef - userRef type: object + x-kubernetes-validations: + - message: ApplicationCredentialResourceSpec is immutable + rule: self == oldSelf required: - cloudCredentialsRef type: object @@ -285,21 +352,81 @@ spec: description: resource contains the observed state of the OpenStack resource. properties: + accessRules: + description: accessRules is a list of fine grained access control + rules + items: + properties: + id: + description: id is the ID of this access rule + maxLength: 1024 + type: string + method: + description: method that the application credential is permitted + to use for a given API endpoint + maxLength: 32 + type: string + path: + description: path that the application credential is permitted + to access + maxLength: 1024 + type: string + service: + description: service type identifier for the service that + the application credential is permitted to access + maxLength: 1024 + type: string + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic description: description: description is a human-readable description for the resource. maxLength: 1024 type: string + expiresAt: + description: expiresAt is the time of expiration for the application + credential. If unset, the application credential does not expire. + format: date-time + type: string name: description: name is a Human-readable name for the resource. Might not be unique. maxLength: 1024 type: string - userID: - description: userID is the ID of the User to which the resource - is associated. + projectID: + description: projectID of the project the application credential + was created for and that authentication requests using this + application credential will be scoped to. maxLength: 1024 type: string + roles: + description: roles is a list of role objects may only contain + roles that the user has assigned on the project + items: + properties: + domainID: + description: domainID of the domain of this role + maxLength: 1024 + type: string + id: + description: id is the ID of a role + maxLength: 1024 + type: string + name: + description: name of an existing role + maxLength: 1024 + type: string + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + unrestricted: + description: unrestricted is a flag indicating whether the application + credential may be used for creation or destruction of other + application credentials or trusts + type: boolean type: object type: object required: diff --git a/internal/osclients/mock/applicationcredential.go b/internal/osclients/mock/applicationcredential.go index 194a49a2f..cecd4726e 100644 --- a/internal/osclients/mock/applicationcredential.go +++ b/internal/osclients/mock/applicationcredential.go @@ -58,32 +58,32 @@ func (m *MockApplicationCredentialClient) EXPECT() *MockApplicationCredentialCli } // CreateApplicationCredential mocks base method. -func (m *MockApplicationCredentialClient) CreateApplicationCredential(ctx context.Context, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { +func (m *MockApplicationCredentialClient) CreateApplicationCredential(ctx context.Context, userID string, opts applicationcredentials.CreateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateApplicationCredential", ctx, opts) + ret := m.ctrl.Call(m, "CreateApplicationCredential", ctx, userID, opts) ret0, _ := ret[0].(*applicationcredentials.ApplicationCredential) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateApplicationCredential indicates an expected call of CreateApplicationCredential. -func (mr *MockApplicationCredentialClientMockRecorder) CreateApplicationCredential(ctx, opts any) *gomock.Call { +func (mr *MockApplicationCredentialClientMockRecorder) CreateApplicationCredential(ctx, userID, opts any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).CreateApplicationCredential), ctx, opts) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).CreateApplicationCredential), ctx, userID, opts) } // DeleteApplicationCredential mocks base method. -func (m *MockApplicationCredentialClient) DeleteApplicationCredential(ctx context.Context, resourceID string) error { +func (m *MockApplicationCredentialClient) DeleteApplicationCredential(ctx context.Context, userID, resourceID string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteApplicationCredential", ctx, resourceID) + ret := m.ctrl.Call(m, "DeleteApplicationCredential", ctx, userID, resourceID) ret0, _ := ret[0].(error) return ret0 } // DeleteApplicationCredential indicates an expected call of DeleteApplicationCredential. -func (mr *MockApplicationCredentialClientMockRecorder) DeleteApplicationCredential(ctx, resourceID any) *gomock.Call { +func (mr *MockApplicationCredentialClientMockRecorder) DeleteApplicationCredential(ctx, userID, resourceID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).DeleteApplicationCredential), ctx, resourceID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).DeleteApplicationCredential), ctx, userID, resourceID) } // GetApplicationCredential mocks base method. @@ -102,30 +102,15 @@ func (mr *MockApplicationCredentialClientMockRecorder) GetApplicationCredential( } // ListApplicationCredentials mocks base method. -func (m *MockApplicationCredentialClient) ListApplicationCredentials(ctx context.Context, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { +func (m *MockApplicationCredentialClient) ListApplicationCredentials(ctx context.Context, userID string, listOpts applicationcredentials.ListOptsBuilder) iter.Seq2[*applicationcredentials.ApplicationCredential, error] { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListApplicationCredentials", ctx, listOpts) + ret := m.ctrl.Call(m, "ListApplicationCredentials", ctx, userID, listOpts) ret0, _ := ret[0].(iter.Seq2[*applicationcredentials.ApplicationCredential, error]) return ret0 } // ListApplicationCredentials indicates an expected call of ListApplicationCredentials. -func (mr *MockApplicationCredentialClientMockRecorder) ListApplicationCredentials(ctx, listOpts any) *gomock.Call { +func (mr *MockApplicationCredentialClientMockRecorder) ListApplicationCredentials(ctx, userID, listOpts any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListApplicationCredentials", reflect.TypeOf((*MockApplicationCredentialClient)(nil).ListApplicationCredentials), ctx, listOpts) -} - -// UpdateApplicationCredential mocks base method. -func (m *MockApplicationCredentialClient) UpdateApplicationCredential(ctx context.Context, id string, opts applicationcredentials.UpdateOptsBuilder) (*applicationcredentials.ApplicationCredential, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateApplicationCredential", ctx, id, opts) - ret0, _ := ret[0].(*applicationcredentials.ApplicationCredential) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// UpdateApplicationCredential indicates an expected call of UpdateApplicationCredential. -func (mr *MockApplicationCredentialClientMockRecorder) UpdateApplicationCredential(ctx, id, opts any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateApplicationCredential", reflect.TypeOf((*MockApplicationCredentialClient)(nil).UpdateApplicationCredential), ctx, id, opts) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListApplicationCredentials", reflect.TypeOf((*MockApplicationCredentialClient)(nil).ListApplicationCredentials), ctx, userID, listOpts) } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrule.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrule.go new file mode 100644 index 000000000..4b485c5cb --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrule.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" +) + +// ApplicationCredentialAccessRuleApplyConfiguration represents a declarative configuration of the ApplicationCredentialAccessRule type for use +// with apply. +type ApplicationCredentialAccessRuleApplyConfiguration struct { + Path *string `json:"path,omitempty"` + Method *apiv1alpha1.HTTPMethod `json:"method,omitempty"` + ServiceRef *apiv1alpha1.KubernetesNameRef `json:"serviceRef,omitempty"` +} + +// ApplicationCredentialAccessRuleApplyConfiguration constructs a declarative configuration of the ApplicationCredentialAccessRule type for use with +// apply. +func ApplicationCredentialAccessRule() *ApplicationCredentialAccessRuleApplyConfiguration { + return &ApplicationCredentialAccessRuleApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *ApplicationCredentialAccessRuleApplyConfiguration) WithPath(value string) *ApplicationCredentialAccessRuleApplyConfiguration { + b.Path = &value + return b +} + +// WithMethod sets the Method field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Method field is set to the value of the last call. +func (b *ApplicationCredentialAccessRuleApplyConfiguration) WithMethod(value apiv1alpha1.HTTPMethod) *ApplicationCredentialAccessRuleApplyConfiguration { + b.Method = &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 *ApplicationCredentialAccessRuleApplyConfiguration) WithServiceRef(value apiv1alpha1.KubernetesNameRef) *ApplicationCredentialAccessRuleApplyConfiguration { + b.ServiceRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrulestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrulestatus.go new file mode 100644 index 000000000..9624a3d3f --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialaccessrulestatus.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 + +// ApplicationCredentialAccessRuleStatusApplyConfiguration represents a declarative configuration of the ApplicationCredentialAccessRuleStatus type for use +// with apply. +type ApplicationCredentialAccessRuleStatusApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Path *string `json:"path,omitempty"` + Method *string `json:"method,omitempty"` + Service *string `json:"service,omitempty"` +} + +// ApplicationCredentialAccessRuleStatusApplyConfiguration constructs a declarative configuration of the ApplicationCredentialAccessRuleStatus type for use with +// apply. +func ApplicationCredentialAccessRuleStatus() *ApplicationCredentialAccessRuleStatusApplyConfiguration { + return &ApplicationCredentialAccessRuleStatusApplyConfiguration{} +} + +// 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 *ApplicationCredentialAccessRuleStatusApplyConfiguration) WithID(value string) *ApplicationCredentialAccessRuleStatusApplyConfiguration { + b.ID = &value + return b +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *ApplicationCredentialAccessRuleStatusApplyConfiguration) WithPath(value string) *ApplicationCredentialAccessRuleStatusApplyConfiguration { + b.Path = &value + return b +} + +// WithMethod sets the Method field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Method field is set to the value of the last call. +func (b *ApplicationCredentialAccessRuleStatusApplyConfiguration) WithMethod(value string) *ApplicationCredentialAccessRuleStatusApplyConfiguration { + b.Method = &value + return b +} + +// WithService sets the Service field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Service field is set to the value of the last call. +func (b *ApplicationCredentialAccessRuleStatusApplyConfiguration) WithService(value string) *ApplicationCredentialAccessRuleStatusApplyConfiguration { + b.Service = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go index 6a7b1e7be..d3d4a136b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialfilter.go @@ -25,9 +25,9 @@ import ( // ApplicationCredentialFilterApplyConfiguration represents a declarative configuration of the ApplicationCredentialFilter type for use // with apply. type ApplicationCredentialFilterApplyConfiguration struct { + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` Description *string `json:"description,omitempty"` - UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` } // ApplicationCredentialFilterApplyConfiguration constructs a declarative configuration of the ApplicationCredentialFilter type for use with @@ -36,6 +36,14 @@ func ApplicationCredentialFilter() *ApplicationCredentialFilterApplyConfiguratio return &ApplicationCredentialFilterApplyConfiguration{} } +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *ApplicationCredentialFilterApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *ApplicationCredentialFilterApplyConfiguration { + b.UserRef = &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. @@ -51,11 +59,3 @@ func (b *ApplicationCredentialFilterApplyConfiguration) WithDescription(value st b.Description = &value return b } - -// WithUserRef sets the UserRef field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the UserRef field is set to the value of the last call. -func (b *ApplicationCredentialFilterApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *ApplicationCredentialFilterApplyConfiguration { - b.UserRef = &value - return b -} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go index f070d2766..fd1c8bc5c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcespec.go @@ -20,14 +20,20 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ApplicationCredentialResourceSpecApplyConfiguration represents a declarative configuration of the ApplicationCredentialResourceSpec type for use // with apply. type ApplicationCredentialResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` + Unrestricted *bool `json:"unrestricted,omitempty"` + SecretRef *apiv1alpha1.KubernetesNameRef `json:"secretRef,omitempty"` + RoleRefs []apiv1alpha1.KubernetesNameRef `json:"roleRefs,omitempty"` + AccessRules []ApplicationCredentialAccessRuleApplyConfiguration `json:"accessRules,omitempty"` + ExpiresAt *v1.Time `json:"expiresAt,omitempty"` } // ApplicationCredentialResourceSpecApplyConfiguration constructs a declarative configuration of the ApplicationCredentialResourceSpec type for use with @@ -59,3 +65,50 @@ func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithUserRef(value b.UserRef = &value return b } + +// WithUnrestricted sets the Unrestricted field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Unrestricted field is set to the value of the last call. +func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithUnrestricted(value bool) *ApplicationCredentialResourceSpecApplyConfiguration { + b.Unrestricted = &value + return b +} + +// WithSecretRef sets the SecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretRef field is set to the value of the last call. +func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithSecretRef(value apiv1alpha1.KubernetesNameRef) *ApplicationCredentialResourceSpecApplyConfiguration { + b.SecretRef = &value + return b +} + +// WithRoleRefs adds the given value to the RoleRefs 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 RoleRefs field. +func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithRoleRefs(values ...apiv1alpha1.KubernetesNameRef) *ApplicationCredentialResourceSpecApplyConfiguration { + for i := range values { + b.RoleRefs = append(b.RoleRefs, values[i]) + } + return b +} + +// WithAccessRules adds the given value to the AccessRules 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 AccessRules field. +func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithAccessRules(values ...*ApplicationCredentialAccessRuleApplyConfiguration) *ApplicationCredentialResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithAccessRules") + } + b.AccessRules = append(b.AccessRules, *values[i]) + } + return b +} + +// WithExpiresAt sets the ExpiresAt field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExpiresAt field is set to the value of the last call. +func (b *ApplicationCredentialResourceSpecApplyConfiguration) WithExpiresAt(value v1.Time) *ApplicationCredentialResourceSpecApplyConfiguration { + b.ExpiresAt = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go index ce52d9d53..04e3eb95a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialresourcestatus.go @@ -18,12 +18,20 @@ limitations under the License. package v1alpha1 +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + // ApplicationCredentialResourceStatusApplyConfiguration represents a declarative configuration of the ApplicationCredentialResourceStatus type for use // with apply. type ApplicationCredentialResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - UserID *string `json:"userID,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Unrestricted *bool `json:"unrestricted,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + Roles []ApplicationCredentialRoleStatusApplyConfiguration `json:"roles,omitempty"` + ExpiresAt *v1.Time `json:"expiresAt,omitempty"` + AccessRules []ApplicationCredentialAccessRuleStatusApplyConfiguration `json:"accessRules,omitempty"` } // ApplicationCredentialResourceStatusApplyConfiguration constructs a declarative configuration of the ApplicationCredentialResourceStatus type for use with @@ -48,10 +56,52 @@ func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithDescription( return b } -// WithUserID sets the UserID field in the declarative configuration to the given value +// WithUnrestricted sets the Unrestricted field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Unrestricted field is set to the value of the last call. +func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithUnrestricted(value bool) *ApplicationCredentialResourceStatusApplyConfiguration { + b.Unrestricted = &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 UserID field is set to the value of the last call. -func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithUserID(value string) *ApplicationCredentialResourceStatusApplyConfiguration { - b.UserID = &value +// If called multiple times, the ProjectID field is set to the value of the last call. +func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithProjectID(value string) *ApplicationCredentialResourceStatusApplyConfiguration { + b.ProjectID = &value + return b +} + +// WithRoles adds the given value to the Roles 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 Roles field. +func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithRoles(values ...*ApplicationCredentialRoleStatusApplyConfiguration) *ApplicationCredentialResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithRoles") + } + b.Roles = append(b.Roles, *values[i]) + } + return b +} + +// WithExpiresAt sets the ExpiresAt field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExpiresAt field is set to the value of the last call. +func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithExpiresAt(value v1.Time) *ApplicationCredentialResourceStatusApplyConfiguration { + b.ExpiresAt = &value + return b +} + +// WithAccessRules adds the given value to the AccessRules 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 AccessRules field. +func (b *ApplicationCredentialResourceStatusApplyConfiguration) WithAccessRules(values ...*ApplicationCredentialAccessRuleStatusApplyConfiguration) *ApplicationCredentialResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithAccessRules") + } + b.AccessRules = append(b.AccessRules, *values[i]) + } return b } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialrolestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialrolestatus.go new file mode 100644 index 000000000..c3e88c115 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialrolestatus.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 + +// ApplicationCredentialRoleStatusApplyConfiguration represents a declarative configuration of the ApplicationCredentialRoleStatus type for use +// with apply. +type ApplicationCredentialRoleStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + DomainID *string `json:"domainID,omitempty"` +} + +// ApplicationCredentialRoleStatusApplyConfiguration constructs a declarative configuration of the ApplicationCredentialRoleStatus type for use with +// apply. +func ApplicationCredentialRoleStatus() *ApplicationCredentialRoleStatusApplyConfiguration { + return &ApplicationCredentialRoleStatusApplyConfiguration{} +} + +// 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 *ApplicationCredentialRoleStatusApplyConfiguration) WithName(value string) *ApplicationCredentialRoleStatusApplyConfiguration { + b.Name = &value + 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 *ApplicationCredentialRoleStatusApplyConfiguration) WithID(value string) *ApplicationCredentialRoleStatusApplyConfiguration { + b.ID = &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 *ApplicationCredentialRoleStatusApplyConfiguration) WithDomainID(value string) *ApplicationCredentialRoleStatusApplyConfiguration { + b.DomainID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 2a454d065..a4da9ab8a 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -217,6 +217,33 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialStatus default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialAccessRule + map: + fields: + - name: method + type: + scalar: string + - name: path + type: + scalar: string + - name: serviceRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialAccessRuleStatus + map: + fields: + - name: id + type: + scalar: string + - name: method + type: + scalar: string + - name: path + type: + scalar: string + - name: service + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialFilter map: fields: @@ -241,25 +268,76 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialResourceSpec map: fields: + - name: accessRules + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialAccessRule + elementRelationship: atomic - name: description type: scalar: string + - name: expiresAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: name type: scalar: string + - name: roleRefs + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: secretRef + type: + scalar: string + - name: unrestricted + type: + scalar: boolean - name: userRef type: scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialResourceStatus map: fields: + - name: accessRules + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialAccessRuleStatus + elementRelationship: atomic - name: description type: scalar: string + - name: expiresAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: name type: scalar: string - - name: userID + - name: projectID + type: + scalar: string + - name: roles + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialRoleStatus + elementRelationship: atomic + - name: unrestricted + type: + scalar: boolean +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialRoleStatus + map: + fields: + - name: domainID + type: + scalar: string + - name: id + type: + scalar: string + - name: name type: scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialSpec diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 373bc2636..54b063b6d 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -58,6 +58,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.AllowedAddressPairStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredential"): return &apiv1alpha1.ApplicationCredentialApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialAccessRule"): + return &apiv1alpha1.ApplicationCredentialAccessRuleApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialAccessRuleStatus"): + return &apiv1alpha1.ApplicationCredentialAccessRuleStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialFilter"): return &apiv1alpha1.ApplicationCredentialFilterApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialImport"): @@ -66,6 +70,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ApplicationCredentialResourceSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialResourceStatus"): return &apiv1alpha1.ApplicationCredentialResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialRoleStatus"): + return &apiv1alpha1.ApplicationCredentialRoleStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialSpec"): return &apiv1alpha1.ApplicationCredentialSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ApplicationCredentialStatus"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 6b3de36a5..572ce3c0b 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -271,9 +271,47 @@ ApplicationCredential is the Schema for an ORC resource. | --- | --- | --- | --- | | `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | | `kind` _string_ | `ApplicationCredential` | | | -| `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` _[ApplicationCredentialSpec](#applicationcredentialspec)_ | spec specifies the desired state of the resource. | | | -| `status` _[ApplicationCredentialStatus](#applicationcredentialstatus)_ | 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` _[ApplicationCredentialSpec](#applicationcredentialspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ApplicationCredentialStatus](#applicationcredentialstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### ApplicationCredentialAccessRule + + + +ApplicationCredentialAccessRule defines an access rule + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [ApplicationCredentialResourceSpec](#applicationcredentialresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `path` _string_ | path that the application credential is permitted to access | | MaxLength: 1024
Optional: \{\}
| +| `method` _[HTTPMethod](#httpmethod)_ | method that the application credential is permitted to use for a given API endpoint | | Enum: [CONNECT DELETE GET HEAD OPTIONS PATCH POST PUT TRACE]
Optional: \{\}
| +| `serviceRef` _[KubernetesNameRef](#kubernetesnameref)_ | serviceRef identifier for the service that the application credential is permitted to access | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### ApplicationCredentialAccessRuleStatus + + + + + + + +_Appears in:_ +- [ApplicationCredentialResourceStatus](#applicationcredentialresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id is the ID of this access rule | | MaxLength: 1024
Optional: \{\}
| +| `path` _string_ | path that the application credential is permitted to access | | MaxLength: 1024
Optional: \{\}
| +| `method` _string_ | method that the application credential is permitted to use for a given API endpoint | | MaxLength: 32
Optional: \{\}
| +| `service` _string_ | service type identifier for the service that the application credential is permitted to access | | MaxLength: 1024
Optional: \{\}
| #### ApplicationCredentialFilter @@ -283,16 +321,16 @@ ApplicationCredential is the Schema for an ORC resource. ApplicationCredentialFilter defines an existing resource by its properties _Validation:_ -- MinProperties: 1 +- MinProperties: 2 _Appears in:_ - [ApplicationCredentialImport](#applicationcredentialimport) | 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
| -| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description of the existing resource | | MaxLength: 1024
Optional: \{\}
| #### ApplicationCredentialImport @@ -311,8 +349,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` _[ApplicationCredentialFilter](#applicationcredentialfilter)_ | 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` _[ApplicationCredentialFilter](#applicationcredentialfilter)_ | 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: 2
Optional: \{\}
| #### ApplicationCredentialResourceSpec @@ -328,9 +366,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 is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
| -| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | 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: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `unrestricted` _boolean_ | unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts | | Optional: \{\}
| +| `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the application credential secret | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `roleRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | roleRefs may only contain roles that the user has assigned on the project. If not provided, the roles assigned to the application credential will be the same as the roles in the current token. | | MaxItems: 256
MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `accessRules` _[ApplicationCredentialAccessRule](#applicationcredentialaccessrule) array_ | accessRules is a list of fine grained access control rules | | MaxItems: 256
MinProperties: 1
Optional: \{\}
| +| `expiresAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | expiresAt is the time of expiration for the application credential. If unset, the application credential does not expire. | | Optional: \{\}
| #### ApplicationCredentialResourceStatus @@ -346,9 +389,31 @@ _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
| -| `userID` _string_ | userID is the ID of the User 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: \{\}
| +| `unrestricted` _boolean_ | unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts | | Optional: \{\}
| +| `projectID` _string_ | projectID of the project the application credential was created for and that authentication requests using this application credential will be scoped to. | | MaxLength: 1024
Optional: \{\}
| +| `roles` _[ApplicationCredentialRoleStatus](#applicationcredentialrolestatus) array_ | roles is a list of role objects may only contain roles that the user has assigned on the project | | MaxItems: 64
Optional: \{\}
| +| `expiresAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | expiresAt is the time of expiration for the application credential. If unset, the application credential does not expire. | | Optional: \{\}
| +| `accessRules` _[ApplicationCredentialAccessRuleStatus](#applicationcredentialaccessrulestatus) array_ | accessRules is a list of fine grained access control rules | | MaxItems: 64
Optional: \{\}
| + + +#### ApplicationCredentialRoleStatus + + + + + + + +_Appears in:_ +- [ApplicationCredentialResourceStatus](#applicationcredentialresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name of an existing role | | MaxLength: 1024
Optional: \{\}
| +| `id` _string_ | id is the ID of a role | | MaxLength: 1024
Optional: \{\}
| +| `domainID` _string_ | domainID of the domain of this role | | MaxLength: 1024
Optional: \{\}
| #### ApplicationCredentialSpec @@ -364,11 +429,11 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[ApplicationCredentialImport](#applicationcredentialimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
| -| `resource` _[ApplicationCredentialResourceSpec](#applicationcredentialresourcespec)_ | 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` _[ApplicationCredentialImport](#applicationcredentialimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ApplicationCredentialResourceSpec](#applicationcredentialresourcespec)_ | 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: \{\}
| #### ApplicationCredentialStatus @@ -384,9 +449,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` _[ApplicationCredentialResourceStatus](#applicationcredentialresourcestatus)_ | 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` _[ApplicationCredentialResourceStatus](#applicationcredentialresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| #### AvailabilityZoneHint @@ -1306,6 +1371,31 @@ _Appears in:_ | `resource` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +#### HTTPMethod + +_Underlying type:_ _string_ + + + +_Validation:_ +- Enum: [CONNECT DELETE GET HEAD OPTIONS PATCH POST PUT TRACE] + +_Appears in:_ +- [ApplicationCredentialAccessRule](#applicationcredentialaccessrule) + +| Field | Description | +| --- | --- | +| `CONNECT` | | +| `DELETE` | | +| `GET` | | +| `HEAD` | | +| `OPTIONS` | | +| `PATCH` | | +| `POST` | | +| `PUT` | | +| `TRACE` | | + + #### HostID @@ -2049,6 +2139,7 @@ _Appears in:_ - [Address](#address) - [AddressScopeFilter](#addressscopefilter) - [AddressScopeResourceSpec](#addressscoperesourcespec) +- [ApplicationCredentialAccessRule](#applicationcredentialaccessrule) - [ApplicationCredentialFilter](#applicationcredentialfilter) - [ApplicationCredentialResourceSpec](#applicationcredentialresourcespec) - [EndpointFilter](#endpointfilter) From 5f92e22a0a7075f2d49a9b62474f7173c5ae7d8c Mon Sep 17 00:00:00 2001 From: Gondermann Date: Wed, 8 Apr 2026 13:00:54 +0200 Subject: [PATCH 141/237] test: Do not hard-code the minimum number of expected filter properties to be exactly "1" On-behalf-of: SAP nils.gondermann@sap.com --- test/apivalidations/common_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/apivalidations/common_test.go b/test/apivalidations/common_test.go index 8300b90f4..1234705aa 100644 --- a/test/apivalidations/common_test.go +++ b/test/apivalidations/common_test.go @@ -108,7 +108,8 @@ func runManagementPolicyTests[PatchT any](getNamespace func() *corev1.Namespace, 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"))) + // Do not force the maximum number of filter properties to be 1 by not hard-coding that string + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("spec.import.filter in body should have at least"))) }) It("should permit valid import filter", func(ctx context.Context) { From 69d05f5ced7417d80d00414fc79fbc7852863a36 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Thu, 12 Feb 2026 15:57:10 +0100 Subject: [PATCH 142/237] ApplicationCredential Tests and Config Samples On-behalf-of: SAP nils.gondermann@sap.com --- ...nstack_v1alpha1_applicationcredential.yaml | 55 ++++++++++++++-- .../00-assert.yaml | 20 +++++- .../00-create-resource.yaml | 49 +++++++++++--- .../00-secret.yaml | 2 + .../00-assert.yaml | 11 +++- .../00-create-resource.yaml | 16 ++--- .../00-secret.yaml | 2 + .../01-delete-secret.yaml | 2 + .../00-assert.yaml | 45 +++++++++++++ .../00-create-resources-missing-deps.yaml | 66 +++++++++++++++---- .../00-secret.yaml | 2 + .../01-assert.yaml | 45 +++++++++++++ .../01-create-dependencies.yaml | 38 +++++++++-- .../03-delete-resources.yaml | 9 +++ .../00-import-resource.yaml | 5 +- .../00-secret.yaml | 4 ++ .../01-create-trap-resource.yaml | 16 ++--- .../02-assert.yaml | 66 +++++++++---------- .../02-create-resource.yaml | 14 ++-- .../00-create-resources.yaml | 20 +++--- .../00-secret.yaml | 2 + .../01-import-resource.yaml | 16 ++++- .../00-import-resource.yaml | 17 ++++- .../00-secret.yaml | 2 + .../01-assert.yaml | 1 - .../01-create-trap-resource.yaml | 15 ++--- .../02-assert.yaml | 1 - .../02-create-resource.yaml | 15 ++--- .../00-assert.yaml | 26 -------- .../00-minimal-resource.yaml | 28 -------- .../00-secret.yaml | 6 -- .../01-assert.yaml | 17 ----- .../01-updated-resource.yaml | 10 --- .../02-assert.yaml | 26 -------- .../02-reverted-resource.yaml | 7 -- .../applicationcredential-update/README.md | 17 ----- .../applicationcredential_test.go | 42 ++++++++---- 37 files changed, 459 insertions(+), 276 deletions(-) delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/01-assert.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml delete mode 100644 internal/controllers/applicationcredential/tests/applicationcredential-update/README.md diff --git a/config/samples/openstack_v1alpha1_applicationcredential.yaml b/config/samples/openstack_v1alpha1_applicationcredential.yaml index 8edc2aba1..d2fca282b 100644 --- a/config/samples/openstack_v1alpha1_applicationcredential.yaml +++ b/config/samples/openstack_v1alpha1_applicationcredential.yaml @@ -1,14 +1,59 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: appcred-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: appcred-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: admin +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: appcred-sample +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + type: "compute" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential metadata: - name: applicationcredential-sample + name: appcred-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 ApplicationCredential - # TODO(scaffolding): Add all fields the resource supports + description: Sample Application Credential + userRef: appcred-sample + unrestricted: true + secretRef: kubernetes-secret + roleRefs: + - appcred-sample + accessRules: + - method: "GET" + serviceRef: appcred-sample + path: "/v2.1/servers" + expiresAt: "2033-03-03T22:22:22Z" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml index 709036a53..917806d94 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-assert.yaml @@ -7,7 +7,12 @@ status: resource: name: applicationcredential-create-full-override description: ApplicationCredential from "create full" test - # TODO(scaffolding): Add all fields the resource supports + unrestricted: true + accessRules: + - method: "GET" + path: "/v2.1/servers" + service: "compute" + expiresAt: "2033-03-03T22:22:22Z" conditions: - type: Available status: "True" @@ -27,7 +32,16 @@ resourceRefs: kind: User name: applicationcredential-create-full ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: applicationcredential-create-full + ref: role assertAll: - celExpr: "applicationcredential.status.id != ''" - - celExpr: "applicationcredential.status.resource.userID == user.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "applicationcredential.status.resource.projectID != ''" + - celExpr: "applicationcredential.status.resource.accessRules.size() == 1" + - celExpr: "applicationcredential.status.resource.accessRules[0].id != ''" + - celExpr: "applicationcredential.status.resource.roles.size() == 1" + - celExpr: "applicationcredential.status.resource.roles[0].id == role.status.id" + - celExpr: "applicationcredential.status.resource.roles[0].name == role.status.resource.name" + - celExpr: "!has(applicationcredential.status.resource.roles[0].domainID)" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml index b95030658..28b2c77d6 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-create-resource.yaml @@ -1,16 +1,42 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: applicationcredential-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: reader +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: name: applicationcredential-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: {} + managementPolicy: unmanaged + import: + filter: + name: admin +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: applicationcredential-create-full +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + type: "compute" --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -18,12 +44,19 @@ metadata: name: applicationcredential-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: applicationcredential-create-full-override description: ApplicationCredential from "create full" test userRef: applicationcredential-create-full - # TODO(scaffolding): Add all fields the resource supports + unrestricted: true + secretRef: applicationcredential-secret + roleRefs: + - applicationcredential-create-full + accessRules: + - method: "GET" + serviceRef: applicationcredential-create-full + path: "/v2.1/servers" + expiresAt: "2033-03-03T22:22:22Z" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml index 045711ee7..fb6d508dd 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-full/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-secret --from-literal=value=abc123 + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml index 0e6d4d81f..a0e819eaa 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-assert.yaml @@ -5,8 +5,9 @@ metadata: name: applicationcredential-create-minimal status: resource: + # Name should default to object name name: applicationcredential-create-minimal - # TODO(scaffolding): Add all fields the resource supports + unrestricted: false conditions: - type: Available status: "True" @@ -28,5 +29,9 @@ resourceRefs: ref: user assertAll: - celExpr: "applicationcredential.status.id != ''" - - celExpr: "applicationcredential.status.resource.userID == user.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "applicationcredential.status.resource.projectID != ''" + - celExpr: "!has(applicationcredential.status.resource.accessRules)" + - celExpr: "!has(applicationcredential.status.resource.description)" + - celExpr: "applicationcredential.status.resource.roles.size() >= 2" + - celExpr: "applicationcredential.status.resource.roles.exists(r, r.name == 'member')" + - celExpr: "applicationcredential.status.resource.roles.exists(r, r.name == 'reader')" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml index f62a6c7b3..ac57fb94d 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-create-resource.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-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: {} + managementPolicy: unmanaged + import: + filter: + name: admin --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -18,11 +18,9 @@ metadata: name: applicationcredential-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: userRef: applicationcredential-create-minimal + secretRef: applicationcredential-secret diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml index 045711ee7..fb6d508dd 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-secret --from-literal=value=abc123 + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml index 1620791b9..07f98457a 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-create-minimal/01-delete-secret.yaml @@ -5,3 +5,5 @@ 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 + - command: kubectl delete secret applicationcredential-secret --wait=false + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml index a4c40956e..6416457dc 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-assert.yaml @@ -16,6 +16,21 @@ status: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-secret-ref +status: + conditions: + - type: Available + message: Waiting for Secret/applicationcredential-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/applicationcredential-dependency-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential metadata: name: applicationcredential-dependency-no-user status: @@ -28,3 +43,33 @@ status: message: Waiting for User/applicationcredential-dependency-pending to be created status: "True" reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-role +status: + conditions: + - type: Available + message: Waiting for Role/applicationcredential-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Role/applicationcredential-dependency-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-service +status: + conditions: + - type: Available + message: Waiting for Service/applicationcredential-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Service/applicationcredential-dependency-pending to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml index 3fec6964c..4dacf814c 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-create-resources-missing-deps.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-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: {} + managementPolicy: unmanaged + import: + filter: + name: admin --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -18,14 +18,12 @@ metadata: name: applicationcredential-dependency-no-user 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: userRef: applicationcredential-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the resource - + secretRef: applicationcredential-secret --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -33,10 +31,54 @@ metadata: name: applicationcredential-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: applicationcredential-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource resource: userRef: applicationcredential-dependency + secretRef: applicationcredential-secret +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-secret-ref +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + userRef: applicationcredential-dependency + secretRef: applicationcredential-dependency-pending +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + userRef: applicationcredential-dependency + secretRef: applicationcredential-secret + roleRefs: + - applicationcredential-dependency-pending +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-service +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + userRef: applicationcredential-dependency + secretRef: applicationcredential-secret + accessRules: + - method: "GET" + serviceRef: applicationcredential-dependency-pending + path: "/v2.1/servers" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml index 045711ee7..fb6d508dd 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-secret --from-literal=value=abc123 + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml index 89e29a72d..42469b154 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-assert.yaml @@ -16,6 +16,21 @@ status: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-secret-ref +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: ApplicationCredential metadata: name: applicationcredential-dependency-no-user status: @@ -28,3 +43,33 @@ status: message: OpenStack resource is up to date status: "False" reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ApplicationCredential +metadata: + name: applicationcredential-dependency-no-role +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: ApplicationCredential +metadata: + name: applicationcredential-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/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml index 75e318e17..360e7b670 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/01-create-dependencies.yaml @@ -4,6 +4,8 @@ kind: TestStep commands: - command: kubectl create secret generic applicationcredential-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-dependency-pending --from-literal=value=abc123 + namespaced: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User @@ -11,9 +13,35 @@ metadata: name: applicationcredential-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: unmanaged + import: + filter: + name: admin +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: applicationcredential-dependency-pending +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: reader +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Service +metadata: + name: applicationcredential-dependency-pending +spec: + cloudCredentialsRef: + cloudName: openstack-admin secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + managementPolicy: unmanaged + import: + filter: + type: "compute" diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml index bed278a33..1f8514050 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-dependency/03-delete-resources.yaml @@ -8,3 +8,12 @@ delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential name: applicationcredential-dependency-no-user +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-dependency-no-secret-ref +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-dependency-no-role +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ApplicationCredential + name: applicationcredential-dependency-no-service diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml index 8877b2e99..8a871cea8 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-import-resource.yaml @@ -5,7 +5,7 @@ metadata: name: applicationcredential-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: @@ -18,9 +18,10 @@ metadata: name: applicationcredential-import-dependency spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: userRef: applicationcredential-import-dependency + description: applicationcredential-import-dependency-target diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml index 045711ee7..b5bec8797 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/00-secret.yaml @@ -4,3 +4,7 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-secret --from-literal=value=abc123 + namespaced: true + - command: kubectl create secret generic applicationcredential-user-password --from-literal=password=abc123 + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml index d3fc3873a..bb5669bf5 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/01-create-trap-resource.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-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: {} + managementPolicy: unmanaged + import: + filter: + name: admin --- # This `applicationcredential-import-dependency-not-this-one` should not be picked by the import filter apiVersion: openstack.k-orc.cloud/v1alpha1 @@ -19,10 +19,10 @@ metadata: name: applicationcredential-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: + description: applicationcredential-import-dependency-target userRef: applicationcredential-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource + secretRef: applicationcredential-secret diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml index 318452565..3c96a9fae 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-assert.yaml @@ -1,34 +1,34 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: ApplicationCredential - name: applicationcredential-import-dependency - ref: applicationcredential1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: ApplicationCredential - name: applicationcredential-import-dependency-not-this-one - ref: applicationcredential2 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: applicationcredential-import-dependency - ref: user -assertAll: - - celExpr: "applicationcredential1.status.id != applicationcredential2.status.id" - - celExpr: "applicationcredential1.status.resource.userID == user.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: ApplicationCredential -metadata: - name: applicationcredential-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 +# FIXME: Need to be able to grant newly created external user permission to create application credentials +# apiVersion: kuttl.dev/v1beta1 +# kind: TestAssert +# resourceRefs: +# - apiVersion: openstack.k-orc.cloud/v1alpha1 +# kind: ApplicationCredential +# name: applicationcredential-import-dependency +# ref: applicationcredential1 +# - apiVersion: openstack.k-orc.cloud/v1alpha1 +# kind: ApplicationCredential +# name: applicationcredential-import-dependency-not-this-one +# ref: applicationcredential2 +# - apiVersion: openstack.k-orc.cloud/v1alpha1 +# kind: User +# name: applicationcredential-import-dependency +# ref: user +# assertAll: +# - celExpr: "applicationcredential1.status.id != applicationcredential2.status.id" +# # --- +# apiVersion: openstack.k-orc.cloud/v1alpha1 +# kind: ApplicationCredential +# metadata: +# name: applicationcredential-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/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml index 2f9738f37..1a6b5cbd4 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-dependency/02-create-resource.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-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: + description: applicationcredential external user + passwordRef: applicationcredential-user-password --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -18,10 +18,10 @@ metadata: name: applicationcredential-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 resource: + description: applicationcredential-import-dependency-target userRef: applicationcredential-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource + secretRef: applicationcredential-secret diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml index 611b974e8..4422d1ca7 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-create-resources.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-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: {} + managementPolicy: unmanaged + import: + filter: + name: admin --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -18,14 +18,13 @@ metadata: name: applicationcredential-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: ApplicationCredential from "import error" test userRef: applicationcredential-import-error - # TODO(scaffolding): add any required field + secretRef: applicationcredential-secret --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -33,11 +32,10 @@ metadata: name: applicationcredential-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: ApplicationCredential from "import error" test userRef: applicationcredential-import-error - # TODO(scaffolding): add any required field + secretRef: applicationcredential-secret diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml index 045711ee7..fb6d508dd 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-secret --from-literal=value=abc123 + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml index e70ca24b4..5cd837952 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import-error/01-import-resource.yaml @@ -1,13 +1,27 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: admin +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential metadata: name: applicationcredential-import-error spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: description: ApplicationCredential from "import error" test + userRef: applicationcredential-import-error diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml index f000ecfd5..39fbd4468 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-import-resource.yaml @@ -1,15 +1,28 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: applicationcredential-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: admin +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential metadata: name: applicationcredential-import spec: cloudCredentialsRef: - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: unmanaged import: filter: name: applicationcredential-import-external description: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test - # TODO(scaffolding): Add all fields supported by the filter + userRef: applicationcredential-import-external diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml index 045711ee7..fb6d508dd 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/00-secret.yaml @@ -4,3 +4,5 @@ kind: TestStep commands: - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} namespaced: true + - command: kubectl create secret generic applicationcredential-secret --from-literal=value=abc123 + namespaced: true diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml index 3e0618259..1beb1c01c 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-assert.yaml @@ -16,7 +16,6 @@ status: resource: name: applicationcredential-import-external-not-this-one description: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test - # TODO(scaffolding): Add fields necessary to match filter --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml index 66edf6003..8af3547db 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/01-create-trap-resource.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-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: {} + managementPolicy: unmanaged + import: + filter: + name: admin --- # This `applicationcredential-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 +21,10 @@ metadata: name: applicationcredential-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: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test userRef: applicationcredential-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter + secretRef: applicationcredential-secret diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml index 7412f2324..7077e0834 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-assert.yaml @@ -30,4 +30,3 @@ status: resource: name: applicationcredential-import-external description: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml index c82fab8b5..3b9666b30 100644 --- a/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml +++ b/internal/controllers/applicationcredential/tests/applicationcredential-import/02-create-resource.yaml @@ -5,12 +5,12 @@ metadata: name: applicationcredential-import 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: {} + managementPolicy: unmanaged + import: + filter: + name: admin --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ApplicationCredential @@ -18,11 +18,10 @@ metadata: name: applicationcredential-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: ApplicationCredential applicationcredential-import-external from "applicationcredential-import" test userRef: applicationcredential-import - # TODO(scaffolding): Add fields necessary to match filter + secretRef: applicationcredential-secret diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml deleted file mode 100644 index fc2cfc9b5..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-assert.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: ApplicationCredential - name: applicationcredential-update - ref: applicationcredential -assertAll: - - celExpr: "!has(applicationcredential.status.resource.description)" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: ApplicationCredential -metadata: - name: applicationcredential-update -status: - resource: - name: applicationcredential-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/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml deleted file mode 100644 index b391c1818..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-minimal-resource.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: applicationcredential-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: ApplicationCredential -metadata: - name: applicationcredential-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: - userRef: applicationcredential-update diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/00-secret.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/applicationcredential/tests/applicationcredential-update/01-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/01-assert.yaml deleted file mode 100644 index bb022f746..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/01-assert.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: ApplicationCredential -metadata: - name: applicationcredential-update -status: - resource: - name: applicationcredential-update-updated - description: applicationcredential-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/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml deleted file mode 100644 index ad1de371a..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/01-updated-resource.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: ApplicationCredential -metadata: - name: applicationcredential-update -spec: - resource: - name: applicationcredential-update-updated - description: applicationcredential-update-updated - # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml deleted file mode 100644 index ec6e12d54..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/02-assert.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: ApplicationCredential - name: applicationcredential-update - ref: applicationcredential -assertAll: - - celExpr: "!has(applicationcredential.status.resource.description)" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: ApplicationCredential -metadata: - name: applicationcredential-update -status: - resource: - name: applicationcredential-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/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml b/internal/controllers/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml deleted file mode 100644 index 2c6c253ff..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/02-reverted-resource.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# 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/applicationcredential/tests/applicationcredential-update/README.md b/internal/controllers/applicationcredential/tests/applicationcredential-update/README.md deleted file mode 100644 index 020723c0e..000000000 --- a/internal/controllers/applicationcredential/tests/applicationcredential-update/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Update ApplicationCredential - -## Step 00 - -Create a ApplicationCredential 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/test/apivalidations/applicationcredential_test.go b/test/apivalidations/applicationcredential_test.go index 615fb4408..4b6bfc785 100644 --- a/test/apivalidations/applicationcredential_test.go +++ b/test/apivalidations/applicationcredential_test.go @@ -42,7 +42,8 @@ func applicationcredentialStub(namespace *corev1.Namespace) *orcv1alpha1.Applica func testApplicationCredentialResource() *applyconfigv1alpha1.ApplicationCredentialResourceSpecApplyConfiguration { return applyconfigv1alpha1.ApplicationCredentialResourceSpec(). - WithUserRef("user") + WithUserRef("user"). + WithSecretRef("applicationcredential-secret") } func baseApplicationCredentialPatch(obj client.Object) *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration { @@ -79,7 +80,7 @@ var _ = Describe("ORC ApplicationCredential API validations", func() { p.Spec.WithImport(applyconfigv1alpha1.ApplicationCredentialImport().WithFilter(applyconfigv1alpha1.ApplicationCredentialFilter())) }, applyValidFilter: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { - p.Spec.WithImport(applyconfigv1alpha1.ApplicationCredentialImport().WithFilter(applyconfigv1alpha1.ApplicationCredentialFilter().WithName("foo"))) + p.Spec.WithImport(applyconfigv1alpha1.ApplicationCredentialImport().WithFilter(applyconfigv1alpha1.ApplicationCredentialFilter().WithName("foo").WithUserRef("user"))) }, applyManaged: func(p *applyconfigv1alpha1.ApplicationCredentialApplyConfiguration) { p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) @@ -105,7 +106,7 @@ var _ = Describe("ORC ApplicationCredential API validations", func() { Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) }) - It("should have immutable userRef", func(ctx context.Context) { + It("should be immutable", func(ctx context.Context) { obj := applicationcredentialStub(namespace) patch := baseApplicationCredentialPatch(obj) patch.Spec.WithResource(testApplicationCredentialResource(). @@ -114,15 +115,32 @@ var _ = Describe("ORC ApplicationCredential API validations", func() { patch.Spec.WithResource(testApplicationCredentialResource(). WithUserRef("user-b")) - Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("userRef is immutable"))) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("ApplicationCredentialResourceSpec is immutable"))) }) - // TODO(scaffolding): Add more resource-specific validation tests. - // Some common things to test: - // - Immutability of fields with `self == oldSelf` validation - // - Enum validation (valid and invalid values) - // - Numeric range validation (min/max bounds) - // - Tag uniqueness (if the resource has tags with listType=set) - // - Format validation (CIDR, UUID, etc.) - // - Cross-field validation rules + DescribeTable("should permit valid http method", + func(ctx context.Context, httpmethod orcv1alpha1.HTTPMethod) { + obj := applicationcredentialStub(namespace) + patch := baseApplicationCredentialPatch(obj) + specPatch := applyconfigv1alpha1.ApplicationCredentialAccessRule().WithMethod(httpmethod) + patch.Spec.WithResource(testApplicationCredentialResource().WithAccessRules(specPatch)) + Expect(applyObj(ctx, obj, patch)).To(Succeed(), "create application credential") + }, + Entry(string(orcv1alpha1.HTTPMethodCONNECT), orcv1alpha1.HTTPMethodCONNECT), + Entry(string(orcv1alpha1.HTTPMethodDELETE), orcv1alpha1.HTTPMethodDELETE), + Entry(string(orcv1alpha1.HTTPMethodGET), orcv1alpha1.HTTPMethodGET), + Entry(string(orcv1alpha1.HTTPMethodHEAD), orcv1alpha1.HTTPMethodHEAD), + Entry(string(orcv1alpha1.HTTPMethodOPTIONS), orcv1alpha1.HTTPMethodOPTIONS), + Entry(string(orcv1alpha1.HTTPMethodPATCH), orcv1alpha1.HTTPMethodPATCH), + Entry(string(orcv1alpha1.HTTPMethodPOST), orcv1alpha1.HTTPMethodPOST), + Entry(string(orcv1alpha1.HTTPMethodPUT), orcv1alpha1.HTTPMethodPUT), + Entry(string(orcv1alpha1.HTTPMethodTRACE), orcv1alpha1.HTTPMethodTRACE), + ) + + It("should not permit invalid http method", func(ctx context.Context) { + obj := applicationcredentialStub(namespace) + patch := baseApplicationCredentialPatch(obj) + patch.Spec.WithResource(testApplicationCredentialResource().WithAccessRules(applyconfigv1alpha1.ApplicationCredentialAccessRule().WithMethod("foo"))) + Expect(applyObj(ctx, obj, patch)).NotTo(Succeed(), "create application credential") + }) }) From 8a64943c8228b7f6f382581db65bb813e44630e6 Mon Sep 17 00:00:00 2001 From: Gondermann Date: Mon, 23 Mar 2026 15:21:41 +0100 Subject: [PATCH 143/237] ApplicationCredential Additional Documentation On-behalf-of: SAP nils.gondermann@sap.com --- README.md | 1 + api/v1alpha1/applicationcredential_types.go | 2 ++ cmd/models-schema/zz_generated.openapi.go | 4 ++-- .../openstack.k-orc.cloud_applicationcredentials.yaml | 10 ++++++---- website/docs/crd-reference.md | 4 ++-- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6c343b281..47f14047d 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ kubectl delete -f $ORC_RELEASE | **controller** | **1.x** | **2.x** | **main** | |:---------------------------:|:-------:|:-------:|:--------:| +| application credential | | ◐ | ◐ | | addressscope | | ✔ | ✔ | | domain | | ✔ | ✔ | | endpoint | | ◐ | ◐ | diff --git a/api/v1alpha1/applicationcredential_types.go b/api/v1alpha1/applicationcredential_types.go index a45805710..bcb11a3dd 100644 --- a/api/v1alpha1/applicationcredential_types.go +++ b/api/v1alpha1/applicationcredential_types.go @@ -65,6 +65,7 @@ type ApplicationCredentialResourceSpec struct { Description *string `json:"description,omitempty"` // userRef is a reference to the ORC User which this resource is associated with. + // Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. // +required UserRef KubernetesNameRef `json:"userRef,omitempty"` @@ -97,6 +98,7 @@ type ApplicationCredentialResourceSpec struct { // +kubebuilder:validation:MinProperties:=2 type ApplicationCredentialFilter struct { // userRef is a reference to the ORC User which this resource is associated with. + // Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. // +required UserRef KubernetesNameRef `json:"userRef,omitempty"` diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index bf0f5a82f..53d77f390 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -1183,7 +1183,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialF Properties: map[string]spec.Schema{ "userRef": { SchemaProps: spec.SchemaProps{ - Description: "userRef is a reference to the ORC User which this resource is associated with.", + Description: "userRef is a reference to the ORC User which this resource is associated with. Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried.", Type: []string{"string"}, Format: "", }, @@ -1311,7 +1311,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialR }, "userRef": { SchemaProps: spec.SchemaProps{ - Description: "userRef is a reference to the ORC User which this resource is associated with.", + Description: "userRef is a reference to the ORC User which this resource is associated with. Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried.", Type: []string{"string"}, Format: "", }, diff --git a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml index f16fe117f..c3d4c5dce 100644 --- a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml @@ -102,8 +102,9 @@ spec: pattern: ^[^,]+$ type: string userRef: - description: userRef is a reference to the ORC User which - this resource is associated with. + description: |- + userRef is a reference to the ORC User which this resource is associated with. + Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. maxLength: 253 minLength: 1 type: string @@ -237,8 +238,9 @@ spec: application credentials or trusts type: boolean userRef: - description: userRef is a reference to the ORC User which this - resource is associated with. + description: |- + userRef is a reference to the ORC User which this resource is associated with. + Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. maxLength: 253 minLength: 1 type: string diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 572ce3c0b..d0b9cde0a 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -328,7 +328,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with.
Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. | | MaxLength: 253
MinLength: 1
Required: \{\}
| | `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| | `description` _string_ | description of the existing resource | | MaxLength: 1024
Optional: \{\}
| @@ -368,7 +368,7 @@ _Appears in:_ | --- | --- | --- | --- | | `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: \{\}
| -| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef is a reference to the ORC User which this resource is associated with.
Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. | | MaxLength: 253
MinLength: 1
Required: \{\}
| | `unrestricted` _boolean_ | unrestricted is a flag indicating whether the application credential may be used for creation or destruction of other application credentials or trusts | | Optional: \{\}
| | `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the application credential secret | | MaxLength: 253
MinLength: 1
Required: \{\}
| | `roleRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | roleRefs may only contain roles that the user has assigned on the project. If not provided, the roles assigned to the application credential will be the same as the roles in the current token. | | MaxItems: 256
MaxLength: 253
MinLength: 1
Optional: \{\}
| From af9033e879643265f4122c2fab0e5e6fadcae841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 16 Apr 2026 15:49:22 +0200 Subject: [PATCH 144/237] Add skill to help with release notes --- .agents/skills/release-notes/SKILL.md | 222 ++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 .agents/skills/release-notes/SKILL.md diff --git a/.agents/skills/release-notes/SKILL.md b/.agents/skills/release-notes/SKILL.md new file mode 100644 index 000000000..a6f6c8a8c --- /dev/null +++ b/.agents/skills/release-notes/SKILL.md @@ -0,0 +1,222 @@ +--- +name: release-notes +description: Draft release notes for a new ORC version. Use when preparing a release to generate changelog and GitHub release body from git history. +disable-model-invocation: true +--- + +# Draft ORC Release Notes + +Guide for drafting release notes when preparing a new ORC release. + +## When to Use + +Use this skill when: +- Preparing a new ORC release +- The user asks to draft or write release notes +- The user asks to prepare a changelog entry + +## Step 1: Gather Release Parameters + +Ask the user for: +1. **New version number** (e.g., `v2.5.0`) +2. **Release date** (default: today) + +Then determine the previous release tag automatically: +```bash +git tag --sort=-v:refname | head -1 +``` + +## Step 2: Collect Git History + +Run these commands to gather the raw data (replace `` with the previous tag): + +```bash +# Full commit log +git log ..HEAD --oneline + +# Contributors with commit counts +git shortlog -sne ..HEAD + +# New controller directories (compare directory listings) +diff <(git ls-tree -d --name-only internal/controllers/ | sort) \ + <(git ls-tree -d --name-only HEAD internal/controllers/ | sort) \ + | grep '^>' + +# All authors who ever contributed before this release +git log --format='%aN' | sort -u > /tmp/old-contributors.txt + +# Authors in this release +git log ..HEAD --format='%aN' | sort -u > /tmp/new-contributors.txt + +# First-time contributors +comm -13 /tmp/old-contributors.txt /tmp/new-contributors.txt +``` + +For each first-time contributor, find the PR number of their first contribution: +```bash +git log ..HEAD --author="" --oneline --reverse | head -1 +``` +Then look up the corresponding PR number from the merge commit message (format: `Merge pull request #NNN`). + +## Step 3: Categorize Changes + +Review every commit and sort into sections. Use these rules: + +### New controllers +Commits that add a new controller directory under `internal/controllers/`. Format: +``` +- : Manage +``` +Examples: +- `Keypair: Manage Nova SSH keypairs` +- `Volume: Manage Cinder block storage volumes` +- `Domain: Manage Keystone identity domains` + +### New features +Feature additions or enhancements to existing controllers or infrastructure. When scoped to a specific controller, prefix with the controller name and colon: +``` +- : +``` +Examples: +- `Server: Added ability to specify SSH keypair` +- `Added support for generating and publishing OLM bundle images` + +### Bug fixes +Bug fixes, especially those referencing GitHub issues. Include the issue link when available: +``` +- (Fixes [#NNN](https://github.com/k-orc/openstack-resource-controller/issues/NNN)) +``` +Examples: +- `Allow to use application credentials with access rules (Fixes [#596](https://github.com/k-orc/openstack-resource-controller/issues/596))` +- `Documentation: Fixed examples in getting-started guide` + +### Breaking changes +API incompatibilities or behavioral changes that require user action. Only include this section if there are breaking changes. List the specific type/field changes. + +### Update considerations +Important information users need to know when upgrading. Only include this section when relevant (e.g., new minimum OpenStack version requirements). + +### Infrastructure improvements +Group related items. Common categories: +- Go version bumps +- Dependency bumps (group into a single bullet: k8s libs, controller-runtime, gophercloud) +- CI changes (OpenStack version support, new test infrastructure) +- Documentation improvements +- Tooling changes + +### Commits to skip +Do NOT include in release notes: +- Changes related to newly introduced controllers: if a controller is new in this release, its features and bug fixes are already implied by the "New controllers" entry and must not be duplicated in "New features" or "Bug fixes" +- Dependabot/automated dependency bumps (summarize as a single infrastructure bullet) +- Merge commits +- Code style fixes, typo fixes, linting fixes +- Internal refactoring with no user-visible impact + +## Step 4: Write the Opening Summary + +The opening summary is optional. Include one when: +- The release has a strong unifying theme +- There is a major new capability worth highlighting + +Format: 1-2 sentences before the first section heading. + +Example from v2.3.0: +> This release brings support for updating resources after creation for all relevant controllers. You can now modify your OpenStack infrastructure in-place without recreating resources, enabling true lifecycle management for production workloads. + +## Step 5: Produce Two Outputs + +### Output 1: GitHub Release Body + +This is the markdown body for the GitHub release (used with `gh release create`). + +Template: +```markdown +## What's Changed + + + +### New controllers + +- Kind1: Description +- Kind2: Description + +### New features + +- Controller: Feature description +- Feature description + +### Bug fixes + +- Fix description (Fixes [#NNN](https://github.com/k-orc/openstack-resource-controller/issues/NNN)) + +### Infrastructure improvements + +- Category: Description + +## New Contributors + +- @username made their first contribution in [#NNN](https://github.com/k-orc/openstack-resource-controller/pull/NNN) + +**Full Changelog**: [...](https://github.com/k-orc/openstack-resource-controller/compare/...) +``` + +Notes: +- Omit any section that has no entries (e.g., skip "Breaking changes" if there are none) +- The "New Contributors" section lists first-time contributors with their GitHub username and first PR +- The "Full Changelog" link uses the GitHub compare URL between the two tags + +### Output 2: Changelog Entry + +This is prepended to `website/docs/changelog.md`, right after the `# Changelog` heading. + +Template: +```markdown +## v. - , + + + +### New controllers + +- Kind1: Description + +### New features + +- Feature description + +### Bug fixes + +- Fix description (Fixes [#NNN](https://github.com/k-orc/openstack-resource-controller/issues/NNN)) + +### Infrastructure improvements + +- Description +``` + +Notes: +- The heading uses `v.` (no patch version) with the full date +- No "New Contributors" or "Full Changelog" sections +- Otherwise identical content to the GitHub release body + +## Step 6: Review Checklist + +Before presenting the draft to the user, verify: + +- [ ] All new controller directories have corresponding entries in "New controllers" +- [ ] All GitHub issue references use the correct issue number and full URL +- [ ] First-time contributors are identified with correct GitHub usernames and PR numbers +- [ ] Dependency bumps are summarized (not listed individually) +- [ ] The compare URL uses the correct previous and new tag names +- [ ] Sections with no entries are omitted entirely +- [ ] Each bullet is concise (one line, no multi-sentence descriptions) +- [ ] Controller-scoped items are prefixed with the controller name +- [ ] The changelog entry heading uses the short version (vX.Y) and the full date +- [ ] The opening summary (if included) accurately represents the release highlights + +## Style Guidelines + +- Use past tense for completed work ("Added", "Fixed", "Bumped") +- Capitalize the first word of each bullet +- End bullets without a period +- Group related changes into a single bullet when possible (especially dependency bumps) +- Use the OpenStack service name to describe new controllers (Nova, Neutron, Cinder, Keystone, Glance) +- Reference specific version numbers for dependency bumps (e.g., "gophercloud to v2.9.0") From 347d8a9313fbf6deb08986b266914d39d07ca7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 16 Apr 2026 15:49:49 +0200 Subject: [PATCH 145/237] README: sort controllers alphabetically --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47f14047d..c3b527d92 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ kubectl delete -f $ORC_RELEASE | **controller** | **1.x** | **2.x** | **main** | |:---------------------------:|:-------:|:-------:|:--------:| -| application credential | | ◐ | ◐ | | addressscope | | ✔ | ✔ | +| application credential | | ◐ | ◐ | | domain | | ✔ | ✔ | | endpoint | | ◐ | ◐ | | flavor | | ✔ | ✔ | From 918370a65c9913a5cade2540c37c2b070abaf9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 16 Apr 2026 15:50:08 +0200 Subject: [PATCH 146/237] Release notes for v2.5.0 --- website/docs/changelog.md | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/website/docs/changelog.md b/website/docs/changelog.md index 2779a1150..f1f0c985f 100644 --- a/website/docs/changelog.md +++ b/website/docs/changelog.md @@ -1,5 +1,46 @@ # Changelog +## v2.5 - April 16, 2026 + +This release adds five new controllers spanning Neutron and Keystone services, +plus significant improvements to existing controllers. This is expected to be +the last v2 release before work begins on v3, which will include minor breaking +API changes. + +### New controllers +- Trunk: Manage Neutron trunk ports +- AddressScope: Manage Neutron address scopes +- Endpoint: Manage Keystone endpoints +- User: Manage Keystone users +- ApplicationCredential: Manage Keystone application credentials + +### New features + +* Port: Added `adminStateUp` and `macAddress` fields +* Port: Added support for port binding +* Server: Added `configdrive` and `metadata` fields +* Volume: Added ability to create bootable volumes from images +* Project: Added ability to specify domainRef +* We now have a process for [lightweight enhancement proposals](https://github.com/k-orc/openstack-resource-controller/tree/main/enhancements). We even got a proposal for [drift detection](https://github.com/k-orc/openstack-resource-controller/blob/main/enhancements/drift-detection.md). + +### Bug fixes + +- SecurityGroup: Fixed inverted error handling for rule creation where retryable errors were marked terminal ([#672](https://github.com/k-orc/openstack-resource-controller/pull/672)) +- SecurityGroup: Fixed availability status by counting security group rules (Fixes [#120](https://github.com/k-orc/openstack-resource-controller/issues/120)) +- RouterInterface: Fixed missing status conditions when routerRef does not exist (Fixes [#314](https://github.com/k-orc/openstack-resource-controller/issues/314)) +- Role: Fixed adoption of domain-scoped roles failing with 409 Conflict ([#733](https://github.com/k-orc/openstack-resource-controller/pull/733)) + +### Infrastructure improvements + +- Go: Bumped to version 1.25.9 +- Bumped dependencies, most notably gophercloud to v2.11.1 +- Added ORC API linter to enforce API design philosophy (no OpenStack IDs in spec fields) +- Added API validation tests for all controllers, with scaffolding support for new controllers +- Added AI agent instructions and skills for assisted development +- CI: Hardened GitHub Actions security (pinned SHAs, scoped permissions, zizmor scanning) +- CI: Added gazpacho, dropped dalmatian testing +* Restored development container image expiration in Quay + ## v2.4 - December 17, 2025 ### New controllers From 1b77db8c935beab04572e4975a492d231c72a1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 16 Apr 2026 17:25:59 +0200 Subject: [PATCH 147/237] Release v2.5.0 --- dist/install.yaml | 5243 ++++++++++++++++++++++++++++++++------------- 1 file changed, 3718 insertions(+), 1525 deletions(-) diff --git a/dist/install.yaml b/dist/install.yaml index b2eefc2f4..5c3d896fa 100644 --- a/dist/install.yaml +++ b/dist/install.yaml @@ -11,17 +11,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: domains.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: addressscopes.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Domain - listKind: DomainList - plural: domains - singular: domain + kind: AddressScope + listKind: AddressScopeList + plural: addressscopes + singular: addressscope scope: Namespaced versions: - additionalPrinterColumns: @@ -40,7 +40,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Domain is the Schema for an ORC resource. + description: AddressScope is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -99,16 +99,31 @@ spec: error state and will not continue to retry. minProperties: 1 properties: - enabled: - description: |- - 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). - type: boolean + ipVersion: + description: ipVersion is the IP protocol version. + enum: + - 4 + - 6 + format: int32 + type: integer name: description: name of the existing resource - maxLength: 64 + 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: |- @@ -116,6 +131,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: @@ -156,24 +172,45 @@ 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 - enabled: - description: |- - 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). - type: boolean + 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: 64 + 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 @@ -272,28 +309,36 @@ 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: 1024 - type: string - enabled: - description: |- - 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). - type: boolean + 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 @@ -304,17 +349,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: flavors.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: applicationcredentials.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Flavor - listKind: FlavorList - plural: flavors - singular: flavor + kind: ApplicationCredential + listKind: ApplicationCredentialList + plural: applicationcredentials + singular: applicationcredential scope: Namespaced versions: - additionalPrinterColumns: @@ -333,7 +378,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Flavor is the Schema for an ORC resource. + description: ApplicationCredential is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -390,30 +435,27 @@ spec: 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 + minProperties: 2 properties: - disk: - description: disk is the size of the root disk in GiB. - format: int32 - minimum: 0 - type: integer + description: + description: description of the existing resource + maxLength: 1024 + type: string name: description: name of the existing resource maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string - ram: - description: ram is the memory of the flavor, measured in - MB. - format: int32 - minimum: 1 - type: integer - vcpus: - description: vcpus is the number of vcpus for the flavor. - format: int32 - minimum: 1 - type: integer + userRef: + description: |- + userRef is a reference to the ORC User which this resource is associated with. + Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. + maxLength: 253 + minLength: 1 + type: string + required: + - userRef type: object id: description: |- @@ -421,6 +463,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: @@ -461,38 +504,54 @@ spec: resource must be specified if the management policy is `managed`. properties: + accessRules: + description: accessRules is a list of fine grained access control + rules + items: + description: ApplicationCredentialAccessRule defines an access + rule + minProperties: 1 + properties: + method: + description: method that the application credential is permitted + to use for a given API endpoint + enum: + - CONNECT + - DELETE + - GET + - HEAD + - OPTIONS + - PATCH + - POST + - PUT + - TRACE + type: string + path: + description: path that the application credential is permitted + to access + maxLength: 1024 + type: string + serviceRef: + description: serviceRef identifier for the service that + the application credential is permitted to access + maxLength: 253 + minLength: 1 + type: string + type: object + maxItems: 256 + type: array + x-kubernetes-list-type: atomic description: - description: description contains a free form description of the - flavor. - maxLength: 65535 + description: description is a human-readable description for the + resource. + maxLength: 255 minLength: 1 type: string - disk: - description: |- - 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. - format: int32 - minimum: 0 - type: integer - ephemeral: - description: |- - 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. - format: int32 - minimum: 0 - type: integer - isPublic: - description: isPublic flags a flavor as being available to all - projects or not. - type: boolean + expiresAt: + description: expiresAt is the time of expiration for the application + credential. If unset, the application credential does not expire. + format: date-time + type: string name: description: |- name will be the name of the created resource. If not specified, the @@ -501,30 +560,42 @@ spec: minLength: 1 pattern: ^[^,]+$ type: string - ram: - description: ram is the memory of the flavor, measured in MB. - format: int32 - minimum: 1 - type: integer - swap: + roleRefs: + description: roleRefs may only contain roles that the user has + assigned on the project. If not provided, the roles assigned + to the application credential will be the same as the roles + in the current token. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + secretRef: + description: secretRef is a reference to a Secret containing the + application credential secret + maxLength: 253 + minLength: 1 + type: string + unrestricted: + description: unrestricted is a flag indicating whether the application + credential may be used for creation or destruction of other + application credentials or trusts + type: boolean + userRef: description: |- - 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. - format: int32 - minimum: 0 - type: integer - vcpus: - description: vcpus is the number of vcpus for the flavor. - format: int32 - minimum: 1 - type: integer + userRef is a reference to the ORC User which this resource is associated with. + Note: Due to the nature of the OpenStack API, managing application credentials for a user different than the one ORC is authenticated against can be computationally expensive. In the worst case, all application credentials of all users have to be queried. + maxLength: 253 + minLength: 1 + type: string required: - - disk - - ram - - vcpus + - secretRef + - userRef type: object x-kubernetes-validations: - - message: FlavorResourceSpec is immutable + - message: ApplicationCredentialResourceSpec is immutable rule: self == oldSelf required: - cloudCredentialsRef @@ -623,71 +694,112 @@ 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: + accessRules: + description: accessRules is a list of fine grained access control + rules + items: + properties: + id: + description: id is the ID of this access rule + maxLength: 1024 + type: string + method: + description: method that the application credential is permitted + to use for a given API endpoint + maxLength: 32 + type: string + path: + description: path that the application credential is permitted + to access + maxLength: 1024 + type: string + service: + description: service type identifier for the service that + the application credential is permitted to access + maxLength: 1024 + type: string + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic description: description: description is a human-readable description for the resource. - maxLength: 65535 + maxLength: 1024 + type: string + expiresAt: + description: expiresAt is the time of expiration for the application + credential. If unset, the application credential does not expire. + format: date-time type: string - disk: - description: disk is the size of the root disk that will be created - in GiB. - format: int32 - type: integer - ephemeral: - description: ephemeral is the size of the ephemeral disk, in GiB. - format: int32 - type: integer - isPublic: - description: isPublic flags a flavor as being available to all - projects or not. - type: boolean name: - description: name is a Human-readable name for the flavor. Might + description: name is a Human-readable name for the resource. Might not be unique. maxLength: 1024 type: string - ram: - description: ram is the memory of the flavor, measured in MB. - format: int32 - type: integer - swap: - description: |- - swap is the size of a dedicated swap disk that will be allocated, in - MiB. - format: int32 - type: integer - vcpus: - description: vcpus is the number of vcpus for the flavor. - format: int32 - type: integer - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 + projectID: + description: projectID of the project the application credential + was created for and that authentication requests using this + application credential will be scoped to. + maxLength: 1024 + type: string + roles: + description: roles is a list of role objects may only contain + roles that the user has assigned on the project + items: + properties: + domainID: + description: domainID of the domain of this role + maxLength: 1024 + type: string + id: + description: id is the ID of a role + maxLength: 1024 + type: string + name: + description: name of an existing role + maxLength: 1024 + type: string + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + unrestricted: + description: unrestricted is a flag indicating whether the application + credential may be used for creation or destruction of other + application credentials or trusts + type: boolean + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: floatingips.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: domains.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: FloatingIP - listKind: FloatingIPList - plural: floatingips - singular: floatingip + kind: Domain + listKind: DomainList + plural: domains + singular: domain scope: Namespaced versions: - additionalPrinterColumns: @@ -699,10 +811,6 @@ spec: jsonPath: .status.conditions[?(@.type=='Available')].status name: Available type: string - - description: Allocated IP address - jsonPath: .status.resource.floatingIP - name: Address - type: string - description: Message describing current progress status jsonPath: .status.conditions[?(@.type=='Progressing')].message name: Message @@ -710,7 +818,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: FloatingIP is the Schema for an ORC resource. + description: Domain is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -769,96 +877,16 @@ spec: error state and will not continue to retry. minProperties: 1 properties: - description: - description: description of the existing resource - maxLength: 255 - minLength: 1 - type: string - floatingIP: - description: floatingIP is the floatingip address. - maxLength: 45 - minLength: 1 - type: string - floatingNetworkRef: - description: floatingNetworkRef is a reference to the ORC - Network which this resource is associated with. - maxLength: 253 - minLength: 1 - 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: + enabled: description: |- - projectRef is a reference to the ORC Project this resource is associated with. - Typically, only used by admin. - maxLength: 253 + 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). + type: boolean + name: + description: name of the existing resource + maxLength: 64 minLength: 1 type: string - status: - description: status is the status of the floatingip. - maxLength: 1024 - 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: |- @@ -866,6 +894,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: @@ -912,80 +941,19 @@ spec: maxLength: 255 minLength: 1 type: string - fixedIP: - description: fixedIP is the IP address of the port to which the - floatingip is associated. - maxLength: 45 - minLength: 1 - type: string - x-kubernetes-validations: - - message: fixedIP is immutable - rule: self == oldSelf - floatingIP: + enabled: description: |- - floatingIP is the IP that will be assigned to the floatingip. If not set, it will - be assigned automatically. - maxLength: 45 - minLength: 1 - type: string - x-kubernetes-validations: - - message: floatingIP is immutable - rule: self == oldSelf - floatingNetworkRef: - description: floatingNetworkRef references the network to which - the floatingip is associated. - maxLength: 253 - minLength: 1 - type: string - x-kubernetes-validations: - - message: floatingNetworkRef is immutable - rule: self == oldSelf - floatingSubnetRef: - description: floatingSubnetRef references the subnet to which - the floatingip is associated. - maxLength: 253 - minLength: 1 - type: string - x-kubernetes-validations: - - message: floatingSubnetRef is immutable - rule: self == oldSelf - 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: + 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). + type: boolean + name: description: |- - projectRef is a reference to the ORC Project this resource is associated with. - Typically, only used by admin. - maxLength: 253 + 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 type: string - x-kubernetes-validations: - - message: projectRef is immutable - rule: self == oldSelf - tags: - description: tags is a list of tags which will be applied to the - floatingip. - 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 - x-kubernetes-validations: - - message: Exactly one of 'floatingNetworkRef' or 'floatingSubnetRef' - must be set - rule: has(self.floatingNetworkRef) != has(self.floatingSubnetRef) required: - cloudCredentialsRef type: object @@ -1083,76 +1051,31 @@ 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: - 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 - fixedIP: - description: fixedIP is the IP address of the port to which the - floatingip is associated. - maxLength: 1024 - type: string - floatingIP: - description: floatingIP is the IP address of the floatingip. - maxLength: 1024 - type: string - floatingNetworkID: - description: floatingNetworkID is the ID of the network to which - the floatingip is associated. - maxLength: 1024 - type: string - portID: - description: portID is the ID of the port to which the floatingip - is associated. - maxLength: 1024 - type: string - projectID: - description: projectID is the project owner of the resource. + enabled: + description: |- + 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). + type: boolean + name: + description: name is a Human-readable name for the resource. Might + not be unique. maxLength: 1024 type: string - revisionNumber: - description: revisionNumber optionally set via extensions/standard-attr-revisions - format: int64 - type: integer - routerID: - description: routerID is the ID of the router to which the floatingip - is associated. - maxLength: 1024 - type: string - status: - description: status indicates the current status of the resource. - maxLength: 1024 - type: string - 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 resource. - 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 + required: + - spec type: object served: true storage: true @@ -1163,17 +1086,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: groups.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: endpoints.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Group - listKind: GroupList - plural: groups - singular: group + kind: Endpoint + listKind: EndpointList + plural: endpoints + singular: endpoint scope: Namespaced versions: - additionalPrinterColumns: @@ -1192,7 +1115,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Group is the Schema for an ORC resource. + description: Endpoint is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -1251,16 +1174,22 @@ spec: 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. + interface: + description: interface of the existing endpoint. + enum: + - admin + - internal + - public + type: string + serviceRef: + description: serviceRef is a reference to the ORC Service + which this resource is associated with. maxLength: 253 minLength: 1 type: string - name: - description: name of the existing resource - maxLength: 64 - minLength: 1 + url: + description: url is the URL of the existing endpoint. + maxLength: 1024 type: string type: object id: @@ -1269,6 +1198,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: @@ -1315,22 +1245,37 @@ spec: maxLength: 255 minLength: 1 type: string - domainRef: - description: domainRef is a reference to the ORC Domain which + x-kubernetes-validations: + - message: description is immutable + rule: self == oldSelf + 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 + 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: domainRef is immutable + - message: serviceRef 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: 64 - minLength: 1 + url: + description: url is the endpoint URL. + maxLength: 1024 type: string + required: + - interface + - serviceRef + - url type: object required: - cloudCredentialsRef @@ -1429,6 +1374,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 @@ -1437,20 +1383,30 @@ spec: description: description: description is a human-readable description for the resource. - maxLength: 1024 + maxLength: 255 + minLength: 1 type: string - domainID: - description: domainID is the ID of the Domain to which the resource + enabled: + description: enabled indicates whether the endpoint is enabled + or not. + type: boolean + interface: + description: interface indicates the visibility of the endpoint. + maxLength: 128 + type: string + serviceID: + description: serviceID is the ID of the Service to which the resource is associated. maxLength: 1024 type: string - name: - description: name is a Human-readable name for the resource. Might - not be unique. + url: + description: url is the endpoint URL. maxLength: 1024 type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -1461,17 +1417,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: images.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: flavors.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Image - listKind: ImageList - plural: images - singular: image + kind: Flavor + listKind: FlavorList + plural: flavors + singular: flavor scope: Namespaced versions: - additionalPrinterColumns: @@ -1490,7 +1446,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Image is the Schema for an ORC resource. + description: Flavor is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -1549,30 +1505,28 @@ spec: error state and will not continue to retry. minProperties: 1 properties: + disk: + description: disk is the size of the root disk in GiB. + format: int32 + minimum: 0 + type: integer name: - description: name specifies the name of a Glance image + description: name of the existing resource maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string - tags: - description: tags is the list of tags on the resource. - items: - maxLength: 255 - minLength: 1 - type: string - maxItems: 64 - type: array - x-kubernetes-list-type: set - visibility: - description: visibility specifies the visibility of a Glance - image. - enum: - - public - - private - - shared - - community - type: string + ram: + description: ram is the memory of the flavor, measured in + MB. + format: int32 + minimum: 1 + type: integer + vcpus: + description: vcpus is the number of vcpus for the flavor. + format: int32 + minimum: 1 + type: integer type: object id: description: |- @@ -1580,6 +1534,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: @@ -1620,114 +1575,1285 @@ spec: resource must be specified if the management policy is `managed`. properties: - content: - description: content specifies how to obtain the image content. - properties: - containerFormat: - default: bare - description: |- - 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. - enum: - - ami - - ari - - aki - - bare - - ovf - - ova - - docker - - compressed - type: string - diskFormat: - description: |- - 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 - type: string - download: - description: |- - download describes how to obtain image data by downloading it from a URL. - Must be set when creating a managed image. - properties: - decompress: - description: |- - 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 - type: string - hash: - description: |- - 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. - properties: - algorithm: - description: algorithm is the hash algorithm used - to generate value. - enum: - - md5 - - sha1 - - sha256 - - sha512 - type: string - value: - description: 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]+$ - type: string - required: - - algorithm - - value - type: object - x-kubernetes-validations: - - message: hash is immutable - rule: self == oldSelf - url: - description: url containing image data - format: uri - maxLength: 2048 - type: string - required: - - url - type: object - required: - - diskFormat - - download - type: object - x-kubernetes-validations: - - message: content is immutable - rule: self == oldSelf + description: + description: description contains a free form description of the + flavor. + maxLength: 65535 + minLength: 1 + type: string + disk: + description: |- + 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. + format: int32 + minimum: 0 + type: integer + ephemeral: + description: |- + 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. + format: int32 + minimum: 0 + type: integer + isPublic: + description: isPublic flags a flavor as being available to all + projects or not. + type: boolean name: description: |- - name will be the name of the created Glance image. If not specified, the - name of the Image object will be used. + 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 - properties: + ram: + description: ram is the memory of the flavor, measured in MB. + format: int32 + minimum: 1 + type: integer + swap: + description: |- + 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. + format: int32 + minimum: 0 + type: integer + vcpus: + description: vcpus is the number of vcpus for the flavor. + format: int32 + minimum: 1 + type: integer + required: + - disk + - ram + - vcpus + type: object + x-kubernetes-validations: + - message: FlavorResourceSpec is immutable + rule: self == oldSelf + 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: + description: + description: description is a human-readable description for the + resource. + maxLength: 65535 + type: string + disk: + description: disk is the size of the root disk that will be created + in GiB. + format: int32 + type: integer + ephemeral: + description: ephemeral is the size of the ephemeral disk, in GiB. + format: int32 + type: integer + isPublic: + description: isPublic flags a flavor as being available to all + projects or not. + type: boolean + name: + description: name is a Human-readable name for the flavor. Might + not be unique. + maxLength: 1024 + type: string + ram: + description: ram is the memory of the flavor, measured in MB. + format: int32 + type: integer + swap: + description: |- + swap is the size of a dedicated swap disk that will be allocated, in + MiB. + format: int32 + type: integer + vcpus: + description: vcpus is the number of vcpus for the flavor. + format: int32 + type: integer + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: floatingips.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: FloatingIP + listKind: FloatingIPList + plural: floatingips + singular: floatingip + 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: Allocated IP address + jsonPath: .status.resource.floatingIP + name: Address + type: string + - description: Message describing current progress status + jsonPath: .status.conditions[?(@.type=='Progressing')].message + name: Message + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: FloatingIP is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + floatingIP: + description: floatingIP is the floatingip address. + maxLength: 45 + minLength: 1 + type: string + floatingNetworkRef: + description: floatingNetworkRef is a reference to the ORC + Network which this resource is associated with. + maxLength: 253 + minLength: 1 + 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 this resource is associated with. + Typically, only used by admin. + maxLength: 253 + minLength: 1 + type: string + status: + description: status is the status of the floatingip. + maxLength: 1024 + 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 + 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: + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + fixedIP: + description: fixedIP is the IP address of the port to which the + floatingip is associated. + maxLength: 45 + minLength: 1 + type: string + x-kubernetes-validations: + - message: fixedIP is immutable + rule: self == oldSelf + floatingIP: + description: |- + floatingIP is the IP that will be assigned to the floatingip. If not set, it will + be assigned automatically. + maxLength: 45 + minLength: 1 + type: string + x-kubernetes-validations: + - message: floatingIP is immutable + rule: self == oldSelf + floatingNetworkRef: + description: floatingNetworkRef references the network to which + the floatingip is associated. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: floatingNetworkRef is immutable + rule: self == oldSelf + floatingSubnetRef: + description: floatingSubnetRef references the subnet to which + the floatingip is associated. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: floatingSubnetRef is immutable + rule: self == oldSelf + 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 this resource is associated with. + Typically, only used by admin. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: projectRef is immutable + rule: self == oldSelf + tags: + description: tags is a list of tags which will be applied to the + floatingip. + 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 + x-kubernetes-validations: + - message: Exactly one of 'floatingNetworkRef' or 'floatingSubnetRef' + must be set + rule: has(self.floatingNetworkRef) != has(self.floatingSubnetRef) + 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: + 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 + fixedIP: + description: fixedIP is the IP address of the port to which the + floatingip is associated. + maxLength: 1024 + type: string + floatingIP: + description: floatingIP is the IP address of the floatingip. + maxLength: 1024 + type: string + floatingNetworkID: + description: floatingNetworkID is the ID of the network to which + the floatingip is associated. + maxLength: 1024 + type: string + portID: + description: portID is the ID of the port to which the floatingip + is associated. + maxLength: 1024 + type: string + projectID: + description: projectID is the project owner of the resource. + maxLength: 1024 + type: string + revisionNumber: + description: revisionNumber optionally set via extensions/standard-attr-revisions + format: int64 + type: integer + routerID: + description: routerID is the ID of the router to which the floatingip + is associated. + maxLength: 1024 + type: string + status: + description: status indicates the current status of the resource. + maxLength: 1024 + type: string + 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 resource. + 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 + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: groups.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: Group + listKind: GroupList + plural: groups + singular: group + 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: Group 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: 64 + minLength: 1 + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + maxLength: 36 + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + 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 + name: + description: |- + 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 + 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: + 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 + 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: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: images.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: Image + listKind: ImageList + plural: images + singular: image + 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: Image 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: + name: + description: name specifies the name of a Glance image + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + tags: + description: tags is the list of tags on the resource. + items: + maxLength: 255 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + visibility: + description: visibility specifies the visibility of a Glance + image. + enum: + - public + - private + - shared + - community + 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: + content: + description: content specifies how to obtain the image content. + properties: + containerFormat: + default: bare + description: |- + 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. + enum: + - ami + - ari + - aki + - bare + - ovf + - ova + - docker + - compressed + type: string + diskFormat: + description: |- + 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 + type: string + download: + description: |- + download describes how to obtain image data by downloading it from a URL. + Must be set when creating a managed image. + properties: + decompress: + description: |- + 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 + type: string + hash: + description: |- + 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. + properties: + algorithm: + description: algorithm is the hash algorithm used + to generate value. + enum: + - md5 + - sha1 + - sha256 + - sha512 + type: string + value: + description: 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]+$ + type: string + required: + - algorithm + - value + type: object + x-kubernetes-validations: + - message: hash is immutable + rule: self == oldSelf + url: + description: url containing image data + format: uri + maxLength: 2048 + type: string + required: + - url + type: object + required: + - diskFormat + - download + type: object + x-kubernetes-validations: + - message: content is immutable + rule: self == oldSelf + name: + description: |- + 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: ^[^,]+$ + type: string + properties: description: properties is metadata available to consumers of the image properties: @@ -2102,6 +3228,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 @@ -2171,6 +3298,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -2181,7 +3310,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 @@ -2282,6 +3411,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: @@ -2443,6 +3573,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 @@ -2467,6 +3598,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -2477,7 +3610,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 @@ -2652,6 +3785,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: @@ -2880,6 +4014,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 @@ -3014,6 +4149,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -3024,7 +4161,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 @@ -3116,11 +4253,20 @@ spec: error state and will not continue to retry. minProperties: 1 properties: + adminStateUp: + description: |- + adminStateUp is the administrative state of the port, + which is up (true) or down (false). + type: boolean description: description: description of the existing resource 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 @@ -3204,6 +4350,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: @@ -3272,6 +4419,12 @@ spec: x-kubernetes-validations: - message: addresses is immutable rule: self == oldSelf + adminStateUp: + default: true + description: |- + adminStateUp is the administrative state of the port, + which is up (true) or down (false). The default value is true. + type: boolean allowedAddressPairs: description: allowedAddressPairs are allowed addresses associated with this port. @@ -3304,6 +4457,43 @@ spec: maxLength: 255 minLength: 1 type: string + hostID: + 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: + 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: 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) + 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. @@ -3491,6 +4681,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 @@ -3563,6 +4754,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 @@ -3626,6 +4821,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -3636,7 +4833,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 @@ -3724,6 +4921,12 @@ spec: 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: 64 @@ -3781,6 +4984,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: @@ -3827,6 +5031,15 @@ spec: maxLength: 65535 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 project is enabled or not. Default is true. @@ -3947,6 +5160,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 @@ -3957,6 +5171,11 @@ spec: resource. maxLength: 65535 type: string + domainID: + description: domainID is the ID of the Domain to which the resource + is associated. + maxLength: 1024 + type: string enabled: description: enabled represents whether a project is enabled or not. @@ -3976,6 +5195,8 @@ spec: x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true @@ -3986,7 +5207,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 @@ -4092,6 +5313,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: @@ -4252,6 +5474,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 @@ -4274,6 +5497,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -4284,7 +5509,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 @@ -4443,6 +5668,8 @@ spec: maxLength: 1024 type: string type: object + required: + - spec type: object served: true storage: true @@ -4453,7 +5680,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 @@ -4623,6 +5850,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: @@ -4846,6 +6074,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 @@ -4908,6 +6137,8 @@ spec: x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true @@ -4918,7 +6149,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 @@ -5088,6 +6319,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: @@ -5117,69 +6349,329 @@ spec: - managed - unmanaged type: string - x-kubernetes-validations: - - message: managementPolicy is immutable - rule: self == oldSelf + 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: + description: + description: description is a human-readable description for the + resource. + maxLength: 255 + minLength: 1 + type: string + name: + description: |- + name will be the name of the created resource. If not specified, the + name of the ORC object will be used. + maxLength: 255 + minLength: 1 + pattern: ^[^,]+$ + type: string + projectRef: + description: |- + projectRef is a reference to the ORC Project this resource is associated with. + Typically, only used by admin. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: projectRef is immutable + rule: self == oldSelf + rules: + description: rules is a list of security group rules belonging + to this SG. + items: + description: SecurityGroupRule defines a Security Group rule + minProperties: 1 + properties: + description: + description: description is a human-readable description + for the resource. + maxLength: 255 + minLength: 1 + type: string + direction: + description: |- + direction represents the direction in which the security group rule + is applied. Can be ingress or egress. + enum: + - ingress + - egress + type: string + ethertype: + description: |- + ethertype must be IPv4 or IPv6, and addresses represented in CIDR + must match the ingress or egress rules. + enum: + - IPv4 + - IPv6 + type: string + portRange: + description: |- + 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 + properties: + max: + description: |- + 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. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + min: + description: |- + 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 + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - max + - min + type: object + protocol: + description: 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 + type: string + remoteIPPrefix: + description: remoteIPPrefix is an IP address block. Should + match the Ethertype (IPv4 or IPv6) + format: cidr + maxLength: 49 + minLength: 1 + type: string + required: + - ethertype + type: object + x-kubernetes-validations: + - message: portRangeMax should be equal or greater than portRange.min + rule: (!has(self.portRange)|| !(self.protocol == 'tcp'|| self.protocol + == 'udp' || self.protocol == 'dccp' || self.protocol == + 'sctp' || self.protocol == 'udplite') || (self.portRange.min + <= self.portRange.max)) + - message: When protocol is ICMP or ICMPv6 portRange.min should + be between 0 and 255 + rule: '!(self.protocol == ''icmp'' || self.protocol == ''icmpv6'') + || !has(self.portRange)|| (self.portRange.min >= 0 && self.portRange.min + <= 255)' + - message: When protocol is ICMP or ICMPv6 portRange.max should + be between 0 and 255 + rule: '!(self.protocol == ''icmp'' || self.protocol == ''icmpv6'') + || !has(self.portRange)|| (self.portRange.max >= 0 && self.portRange.max + <= 255)' + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + stateful: + description: stateful indicates if the security group is stateful + or stateless. + type: boolean + x-kubernetes-validations: + - message: stateful is immutable + rule: self == oldSelf + tags: + description: tags is a list of tags which will be applied to the + security group. + 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 + 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 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`. + description: resource contains the observed state of the OpenStack + resource. properties: + 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: 255 - minLength: 1 + maxLength: 1024 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: ^[^,]+$ + description: name is a Human-readable name for the security group. + Might not be unique. + maxLength: 1024 type: string - projectRef: - description: |- - projectRef is a reference to the ORC Project this resource is associated with. - Typically, only used by admin. - maxLength: 253 - minLength: 1 + projectID: + description: projectID is the project owner of the security group. + maxLength: 1024 type: string - x-kubernetes-validations: - - message: projectRef is immutable - rule: self == oldSelf + revisionNumber: + description: revisionNumber optionally set via extensions/standard-attr-revisions + format: int64 + type: integer rules: description: rules is a list of security group rules belonging to this SG. items: - description: SecurityGroupRule defines a Security Group rule - minProperties: 1 properties: description: description: description is a human-readable description for the resource. - maxLength: 255 - minLength: 1 + maxLength: 1024 type: string direction: description: |- direction represents the direction in which the security group rule is applied. Can be ingress or egress. - enum: - - ingress - - egress + maxLength: 1024 type: string ethertype: description: |- ethertype must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules. - enum: - - IPv4 - - IPv6 + maxLength: 1024 + type: string + id: + description: id is the ID of the security group rule. + maxLength: 1024 type: string portRange: description: |- @@ -5195,8 +6687,6 @@ spec: 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. format: int32 - maximum: 65535 - minimum: 0 type: integer min: description: |- @@ -5204,67 +6694,26 @@ spec: 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 format: int32 - maximum: 65535 - minimum: 0 type: integer - required: - - max - - min type: object protocol: - description: 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 + description: |- + protocol is the IP protocol can be represented by a string, an + integer, or null + maxLength: 1024 + type: string + remoteGroupID: + description: |- + remoteGroupID is the remote group UUID to associate with this security group rule + RemoteGroupID + maxLength: 1024 type: string remoteIPPrefix: description: remoteIPPrefix is an IP address block. Should match the Ethertype (IPv4 or IPv6) - format: cidr - maxLength: 49 - minLength: 1 + maxLength: 1024 type: string - required: - - ethertype type: object - x-kubernetes-validations: - - message: portRangeMax should be equal or greater than portRange.min - rule: (!has(self.portRange)|| !(self.protocol == 'tcp'|| self.protocol - == 'udp' || self.protocol == 'dccp' || self.protocol == - 'sctp' || self.protocol == 'udplite') || (self.portRange.min - <= self.portRange.max)) - - message: When protocol is ICMP or ICMPv6 portRange.min should - be between 0 and 255 - rule: '!(self.protocol == ''icmp'' || self.protocol == ''icmpv6'') - || !has(self.portRange)|| (self.portRange.min >= 0 && self.portRange.min - <= 255)' - - message: When protocol is ICMP or ICMPv6 portRange.max should - be between 0 and 255 - rule: '!(self.protocol == ''icmp'' || self.protocol == ''icmpv6'') - || !has(self.portRange)|| (self.portRange.max >= 0 && self.portRange.max - <= 255)' maxItems: 256 type: array x-kubernetes-list-type: atomic @@ -5272,23 +6721,211 @@ spec: description: stateful indicates if the security group is stateful or stateless. type: boolean - x-kubernetes-validations: - - message: stateful is immutable - rule: self == oldSelf tags: - description: tags is a list of tags which will be applied to the - security group. + description: tags is the list of tags on the resource. items: - description: |- - NeutronTag represents a tag on a Neutron resource. - It may not be empty and may not contain commas. - maxLength: 255 - minLength: 1 + maxLength: 1024 type: string maxItems: 64 type: array - x-kubernetes-list-type: set + x-kubernetes-list-type: atomic + updatedAt: + description: updatedAt shows the date and time when the resource + was updated. The date and time stamp format is ISO 8601 + format: date-time + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: servergroups.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: ServerGroup + listKind: ServerGroupList + plural: servergroups + singular: servergroup + 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: ServerGroup 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: + 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: + 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 + policy: + description: policy is the policy to use for the server group. + enum: + - affinity + - anti-affinity + - soft-affinity + - soft-anti-affinity + type: string + rules: + description: rules is the rules to use for the server group. + properties: + maxServerPerHost: + description: |- + maxServerPerHost specifies how many servers can reside on a single compute host. + It can be used only with the "anti-affinity" policy. + format: int32 + type: integer + type: object + required: + - policy type: object + x-kubernetes-validations: + - message: ServerGroupResourceSpec is immutable + rule: self == oldSelf + - message: maxServerPerHost can only be used with the anti-affinity + policy + rule: 'has(self.rules) && self.rules.maxServerPerHost > 0 ? self.policy + == ''anti-affinity'' : true' required: - cloudCredentialsRef type: object @@ -5374,135 +7011,55 @@ spec: 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: - 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 security group. - Might not be unique. - maxLength: 1024 - type: string - projectID: - description: projectID is the project owner of the security group. - maxLength: 1024 - type: string - revisionNumber: - description: revisionNumber optionally set via extensions/standard-attr-revisions - format: int64 - type: integer - rules: - description: rules is a list of security group rules belonging - to this SG. - items: - properties: - description: - description: description is a human-readable description - for the resource. - maxLength: 1024 - type: string - direction: - description: |- - direction represents the direction in which the security group rule - is applied. Can be ingress or egress. - maxLength: 1024 - type: string - ethertype: - description: |- - ethertype must be IPv4 or IPv6, and addresses represented in CIDR - must match the ingress or egress rules. - maxLength: 1024 - type: string - id: - description: id is the ID of the security group rule. - maxLength: 1024 - type: string - portRange: - description: |- - 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 - properties: - max: - description: |- - 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. - format: int32 - type: integer - min: - description: |- - 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 - format: int32 - type: integer - type: object - protocol: - description: |- - protocol is the IP protocol can be represented by a string, an - integer, or null - maxLength: 1024 - type: string - remoteGroupID: - description: |- - remoteGroupID is the remote group UUID to associate with this security group rule - RemoteGroupID - maxLength: 1024 - type: string - remoteIPPrefix: - description: remoteIPPrefix is an IP address block. Should - match the Ethertype (IPv4 or IPv6) - maxLength: 1024 - type: string - type: object - maxItems: 256 - type: array - x-kubernetes-list-type: atomic - stateful: - description: stateful indicates if the security group is stateful - or stateless. - type: boolean - tags: - description: tags is the list of tags on the resource. - items: - maxLength: 1024 - type: string - maxItems: 64 - type: array - x-kubernetes-list-type: atomic - updatedAt: - description: updatedAt shows the date and time when the resource - was updated. The date and time stamp format is ISO 8601 - format: date-time + - 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: + name: + description: name is a Human-readable name for the servergroup. + Might not be unique. + maxLength: 1024 + type: string + policy: + description: policy is the policy of the servergroup. + maxLength: 1024 + type: string + projectID: + description: projectID is the project owner of the resource. + maxLength: 1024 + type: string + rules: + description: rules is the rules of the server group. + properties: + maxServerPerHost: + description: |- + maxServerPerHost specifies how many servers can reside on a single compute host. + It can be used only with the "anti-affinity" policy. + format: int32 + type: integer + type: object + userID: + description: userID of the server group. + maxLength: 1024 type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -5513,17 +7070,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: servergroups.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: servers.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: ServerGroup - listKind: ServerGroupList - plural: servergroups - singular: servergroup + kind: Server + listKind: ServerList + plural: servers + singular: server scope: Namespaced versions: - additionalPrinterColumns: @@ -5542,7 +7099,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: ServerGroup is the Schema for an ORC resource. + description: Server is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -5601,12 +7158,62 @@ spec: error state and will not continue to retry. minProperties: 1 properties: + availabilityZone: + description: availabilityZone is the availability zone of + the existing resource + maxLength: 255 + type: string name: description: name of the existing resource maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string + notTags: + description: |- + notTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + maxLength: 80 + minLength: 1 + type: string + maxItems: 50 + type: array + x-kubernetes-list-type: set + notTagsAny: + description: |- + notTagsAny is a list of tags to filter by. If specified, resources + which contain any of the given tags will be excluded from the result. + items: + maxLength: 80 + minLength: 1 + type: string + maxItems: 50 + type: array + x-kubernetes-list-type: set + tags: + description: |- + tags is a list of tags to filter by. If specified, the resource must + have all of the tags specified to be included in the result. + items: + maxLength: 80 + minLength: 1 + type: string + maxItems: 50 + type: array + x-kubernetes-list-type: set + tagsAny: + description: |- + tagsAny is a list of tags to filter by. If specified, the resource + must have at least one of the tags specified to be included in the + result. + items: + maxLength: 80 + minLength: 1 + type: string + maxItems: 50 + type: array + x-kubernetes-list-type: set type: object id: description: |- @@ -5614,6 +7221,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: @@ -5654,6 +7262,76 @@ spec: resource must be specified if the management policy is `managed`. properties: + availabilityZone: + description: availabilityZone is the availability zone in which + to create the server. + maxLength: 255 + type: string + 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. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: flavorRef is immutable + rule: self == oldSelf + imageRef: + description: |- + 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 + type: string + x-kubernetes-validations: + - message: imageRef is immutable + rule: self == oldSelf + keypairRef: + description: |- + keypairRef is a reference to a KeyPair object. The server will be + created with this keypair for SSH access. + maxLength: 253 + minLength: 1 + type: string + 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 @@ -5662,34 +7340,91 @@ spec: minLength: 1 pattern: ^[^,]+$ type: string - policy: - description: policy is the policy to use for the server group. - enum: - - affinity - - anti-affinity - - soft-affinity - - soft-anti-affinity + ports: + description: ports defines a list of ports which will be attached + to the server. + items: + maxProperties: 1 + minProperties: 1 + properties: + portRef: + description: |- + portRef is a reference to a Port object. Server creation will wait for + this port to be created and available. + maxLength: 253 + minLength: 1 + type: string + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + serverGroupRef: + description: |- + serverGroupRef is a reference to a ServerGroup object. The server + will be created in the server group. + maxLength: 253 + minLength: 1 type: string - rules: - description: rules is the rules to use for the server group. + x-kubernetes-validations: + - message: serverGroupRef is immutable + rule: self == oldSelf + tags: + description: tags is a list of tags which will be applied to the + server. + items: + maxLength: 80 + minLength: 1 + type: string + maxItems: 50 + type: array + x-kubernetes-list-type: set + userData: + description: |- + 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 properties: - maxServerPerHost: - description: |- - maxServerPerHost specifies how many servers can reside on a single compute host. - It can be used only with the "anti-affinity" policy. - format: int32 - type: integer + secretRef: + description: secretRef is a reference to a Secret containing + the user data for this server. + maxLength: 253 + minLength: 1 + type: string type: object + x-kubernetes-validations: + - message: userData is immutable + rule: self == oldSelf + volumes: + description: volumes is a list of volumes attached to the server. + items: + minProperties: 1 + properties: + device: + description: |- + device is the name of the device, such as `/dev/vdb`. + Omit for auto-assignment + maxLength: 255 + type: string + volumeRef: + description: |- + volumeRef is a reference to a Volume object. Server creation will wait for + this volume to be created and available. + maxLength: 253 + minLength: 1 + type: string + required: + - volumeRef + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic required: - - policy + - flavorRef + - imageRef + - ports type: object - x-kubernetes-validations: - - message: ServerGroupResourceSpec is immutable - rule: self == oldSelf - - message: maxServerPerHost can only be used with the anti-affinity - policy - rule: 'has(self.rules) && self.rules.maxServerPerHost > 0 ? self.policy - == ''anti-affinity'' : true' required: - cloudCredentialsRef type: object @@ -5787,40 +7522,143 @@ 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: - name: - description: name is a Human-readable name for the servergroup. - Might not be unique. + availabilityZone: + description: availabilityZone is the availability zone where the + server is located. maxLength: 1024 type: string - policy: - description: policy is the policy of the servergroup. + 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. maxLength: 1024 type: string - projectID: - description: projectID is the project owner of the resource. + imageID: + description: imageID indicates the OS image used to deploy the + server. maxLength: 1024 type: string - rules: - description: rules is the rules of the server group. - properties: - maxServerPerHost: - description: |- - maxServerPerHost specifies how many servers can reside on a single compute host. - It can be used only with the "anti-affinity" policy. - format: int32 - type: integer - type: object - userID: - description: userID of the server group. + interfaces: + description: interfaces contains the list of interfaces attached + to the server. + items: + properties: + fixedIPs: + description: fixedIPs is the list of fixed IP addresses + assigned to the interface. + items: + properties: + ipAddress: + description: ipAddress is the IP address assigned + to the port. + maxLength: 1024 + type: string + subnetID: + description: subnetID is the ID of the subnet from + which the IP address is allocated. + maxLength: 1024 + type: string + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + macAddr: + description: macAddr is the MAC address of the interface. + maxLength: 1024 + type: string + netID: + description: netID is the ID of the network to which the + interface is attached. + maxLength: 1024 + type: string + portID: + description: portID is the ID of a port attached to the + server. + maxLength: 1024 + type: string + portState: + description: portState is the state of the port (e.g., ACTIVE, + DOWN). + maxLength: 1024 + type: string + type: object + 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. + maxLength: 1024 + type: string + serverGroups: + description: |- + 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. + items: + maxLength: 1024 + type: string + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + status: + description: |- + status contains the current operational status of the server, + such as IN_PROGRESS or ACTIVE. maxLength: 1024 type: string + tags: + description: tags is the list of tags on the resource. + items: + maxLength: 1024 + type: string + maxItems: 50 + type: array + x-kubernetes-list-type: atomic + volumes: + description: volumes contains the volumes attached to the server. + items: + properties: + id: + description: id is the ID of a volume attached to the server. + maxLength: 1024 + type: string + type: object + maxItems: 64 + type: array + x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true @@ -5831,17 +7669,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: servers.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: services.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Server - listKind: ServerList - plural: servers - singular: server + kind: Service + listKind: ServiceList + plural: services + singular: service scope: Namespaced versions: - additionalPrinterColumns: @@ -5860,7 +7698,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Server is the Schema for an ORC resource. + description: Service is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -5919,62 +7757,17 @@ spec: error state and will not continue to retry. minProperties: 1 properties: - availabilityZone: - description: availabilityZone is the availability zone of - the existing resource - maxLength: 255 - type: string name: description: name of the existing resource maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string - notTags: - description: |- - notTags is a list of tags to filter by. If specified, resources which - contain all of the given tags will be excluded from the result. - items: - maxLength: 80 - minLength: 1 - type: string - maxItems: 50 - type: array - x-kubernetes-list-type: set - notTagsAny: - description: |- - notTagsAny is a list of tags to filter by. If specified, resources - which contain any of the given tags will be excluded from the result. - items: - maxLength: 80 - minLength: 1 - type: string - maxItems: 50 - type: array - x-kubernetes-list-type: set - tags: - description: |- - tags is a list of tags to filter by. If specified, the resource must - have all of the tags specified to be included in the result. - items: - maxLength: 80 - minLength: 1 - type: string - maxItems: 50 - type: array - x-kubernetes-list-type: set - tagsAny: - description: |- - tagsAny is a list of tags to filter by. If specified, the resource - must have at least one of the tags specified to be included in the - result. - items: - maxLength: 80 - minLength: 1 - type: string - maxItems: 50 - type: array - x-kubernetes-list-type: set + type: + description: type of the existing resource + maxLength: 255 + minLength: 1 + type: string type: object id: description: |- @@ -5982,6 +7775,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: @@ -6004,153 +7798,50 @@ spec: 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: - availabilityZone: - description: availabilityZone is the availability zone in which - to create the server. - maxLength: 255 - type: string - x-kubernetes-validations: - - message: availabilityZone is immutable - rule: self == oldSelf - flavorRef: - description: flavorRef references the flavor to use for the server - instance. - maxLength: 253 - minLength: 1 - type: string - x-kubernetes-validations: - - message: flavorRef is immutable - rule: self == oldSelf - imageRef: - description: |- - 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 - type: string - x-kubernetes-validations: - - message: imageRef is immutable - rule: self == oldSelf - keypairRef: - description: |- - keypairRef is a reference to a KeyPair object. The server will be - created with this keypair for SSH access. - maxLength: 253 + `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: + description: + description: description indicates the description of service. + maxLength: 255 minLength: 1 type: string - x-kubernetes-validations: - - message: keypairRef is immutable - rule: self == oldSelf + enabled: + default: true + description: enabled indicates whether the service is enabled + or not. + 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. + name indicates the name of service. If not specified, the name of the ORC + resource will be used. maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string - ports: - description: ports defines a list of ports which will be attached - to the server. - items: - maxProperties: 1 - minProperties: 1 - properties: - portRef: - description: |- - portRef is a reference to a Port object. Server creation will wait for - this port to be created and available. - maxLength: 253 - minLength: 1 - type: string - type: object - maxItems: 64 - type: array - x-kubernetes-list-type: atomic - serverGroupRef: - description: |- - serverGroupRef is a reference to a ServerGroup object. The server - will be created in the server group. - maxLength: 253 + type: + description: type indicates which resource the service is responsible + for. + maxLength: 255 minLength: 1 type: string - x-kubernetes-validations: - - message: serverGroupRef is immutable - rule: self == oldSelf - tags: - description: tags is a list of tags which will be applied to the - server. - items: - maxLength: 80 - minLength: 1 - type: string - maxItems: 50 - type: array - x-kubernetes-list-type: set - userData: - description: |- - 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 - properties: - secretRef: - description: secretRef is a reference to a Secret containing - the user data for this server. - maxLength: 253 - minLength: 1 - type: string - type: object - x-kubernetes-validations: - - message: userData is immutable - rule: self == oldSelf - volumes: - description: volumes is a list of volumes attached to the server. - items: - minProperties: 1 - properties: - device: - description: |- - device is the name of the device, such as `/dev/vdb`. - Omit for auto-assignment - maxLength: 255 - type: string - volumeRef: - description: |- - volumeRef is a reference to a Volume object. Server creation will wait for - this volume to be created and available. - maxLength: 253 - minLength: 1 - type: string - required: - - volumeRef - type: object - maxItems: 64 - type: array - x-kubernetes-list-type: atomic required: - - flavorRef - - imageRef - - ports + - type type: object required: - cloudCredentialsRef @@ -6249,117 +7940,33 @@ 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: - availabilityZone: - description: availabilityZone is the availability zone where the - server is located. - maxLength: 1024 - type: string - hostID: - description: hostID is the host where the server is located in - the cloud. - maxLength: 1024 - type: string - imageID: - description: imageID indicates the OS image used to deploy the - server. - maxLength: 1024 + description: + description: description indicates the description of service. + maxLength: 255 type: string - interfaces: - description: interfaces contains the list of interfaces attached - to the server. - items: - properties: - fixedIPs: - description: fixedIPs is the list of fixed IP addresses - assigned to the interface. - items: - properties: - ipAddress: - description: ipAddress is the IP address assigned - to the port. - maxLength: 1024 - type: string - subnetID: - description: subnetID is the ID of the subnet from - which the IP address is allocated. - maxLength: 1024 - type: string - type: object - maxItems: 32 - type: array - x-kubernetes-list-type: atomic - macAddr: - description: macAddr is the MAC address of the interface. - maxLength: 1024 - type: string - netID: - description: netID is the ID of the network to which the - interface is attached. - maxLength: 1024 - type: string - portID: - description: portID is the ID of a port attached to the - server. - maxLength: 1024 - type: string - portState: - description: portState is the state of the port (e.g., ACTIVE, - DOWN). - maxLength: 1024 - type: string - type: object - maxItems: 64 - type: array - x-kubernetes-list-type: atomic + enabled: + description: enabled indicates whether the service is enabled + or not. + type: boolean name: - description: name is the human-readable name of the resource. - Might not be unique. - maxLength: 1024 + description: name indicates the name of service. + maxLength: 255 type: string - serverGroups: - description: |- - 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. - items: - maxLength: 1024 - type: string - maxItems: 32 - type: array - x-kubernetes-list-type: atomic - status: - description: |- - status contains the current operational status of the server, - such as IN_PROGRESS or ACTIVE. - maxLength: 1024 + type: + description: type indicates which resource the service is responsible + for. + maxLength: 255 type: string - tags: - description: tags is the list of tags on the resource. - items: - maxLength: 1024 - type: string - maxItems: 50 - type: array - x-kubernetes-list-type: atomic - volumes: - description: volumes contains the volumes attached to the server. - items: - properties: - id: - description: id is the ID of a volume attached to the server. - maxLength: 1024 - type: string - type: object - maxItems: 64 - type: array - x-kubernetes-list-type: atomic type: object type: object + required: + - spec type: object served: true storage: true @@ -6370,17 +7977,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: services.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: subnets.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Service - listKind: ServiceList - plural: services - singular: service + kind: Subnet + listKind: SubnetList + plural: subnets + singular: subnet scope: Namespaced versions: - additionalPrinterColumns: @@ -6399,7 +8006,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Service is the Schema for an ORC resource. + description: Subnet is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -6458,17 +8065,128 @@ spec: error state and will not continue to retry. minProperties: 1 properties: + cidr: + description: cidr of the existing resource + format: cidr + maxLength: 49 + minLength: 1 + type: string + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + gatewayIP: + description: gatewayIP is the IP address of the gateway of + the existing resource + maxLength: 45 + minLength: 1 + type: string + ipVersion: + description: ipVersion of the existing resource + enum: + - 4 + - 6 + format: int32 + type: integer + ipv6: + description: ipv6 options of the existing resource + minProperties: 1 + properties: + addressMode: + description: addressMode specifies mechanisms for assigning + IPv6 IP addresses. + enum: + - slaac + - dhcpv6-stateful + - dhcpv6-stateless + type: string + raMode: + description: |- + raMode specifies the IPv6 router advertisement mode. It specifies whether + the networking service should transmit ICMPv6 packets. + enum: + - slaac + - dhcpv6-stateful + - dhcpv6-stateless + type: string + type: object name: description: name of the existing resource maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string - type: - description: type of the existing resource - maxLength: 255 + networkRef: + description: networkRef is a reference to the ORC Network + which this subnet is associated with. + maxLength: 253 + minLength: 1 + type: string + notTags: + description: |- + notTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + maxLength: 255 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + notTagsAny: + description: |- + notTagsAny is a list of tags to filter by. If specified, resources + which contain any of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + maxLength: 255 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + projectRef: + description: |- + projectRef is a reference to the ORC Project this resource is associated with. + Typically, only used by admin. + 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: |- @@ -6476,6 +8194,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: @@ -6516,32 +8235,210 @@ spec: resource must be specified if the management policy is `managed`. properties: + allocationPools: + description: |- + allocationPools are IP Address pools that will be available for DHCP. IP + addresses must be in CIDR. + items: + properties: + end: + description: end is the last IP address in the allocation + pool. + maxLength: 45 + minLength: 1 + type: string + start: + description: start is the first IP address in the allocation + pool. + maxLength: 45 + minLength: 1 + type: string + required: + - end + - start + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + cidr: + description: cidr is the address CIDR of the subnet. It must match + the IP version specified in IPVersion. + format: cidr + maxLength: 49 + minLength: 1 + type: string + x-kubernetes-validations: + - message: cidr is immutable + rule: self == oldSelf description: - description: description indicates the description of service. + description: description is a human-readable description for the + resource. maxLength: 255 minLength: 1 type: string - enabled: - default: true - description: enabled indicates whether the service is enabled - or not. + dnsNameservers: + description: dnsNameservers are the nameservers to be set via + DHCP. + items: + maxLength: 45 + minLength: 1 + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + dnsPublishFixedIP: + description: |- + dnsPublishFixedIP will either enable or disable the publication of + fixed IPs to the DNS. Defaults to false. + type: boolean + x-kubernetes-validations: + - message: dnsPublishFixedIP is immutable + rule: self == oldSelf + enableDHCP: + description: enableDHCP will either enable to disable the DHCP + service. type: boolean + gateway: + description: |- + 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. + properties: + ip: + description: |- + 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: string + type: + description: |- + 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 + type: string + required: + - type + type: object + hostRoutes: + description: hostRoutes are any static host routes to be set via + DHCP. + items: + properties: + destination: + description: destination for the additional route. + format: cidr + maxLength: 49 + minLength: 1 + type: string + nextHop: + description: nextHop for the additional route. + maxLength: 45 + minLength: 1 + type: string + required: + - destination + - nextHop + type: object + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + ipVersion: + description: ipVersion is the IP version for the subnet. + enum: + - 4 + - 6 + format: int32 + type: integer + x-kubernetes-validations: + - message: ipVersion is immutable + rule: self == oldSelf + ipv6: + description: ipv6 contains IPv6-specific options. It may only + be set if IPVersion is 6. + minProperties: 1 + properties: + addressMode: + description: addressMode specifies mechanisms for assigning + IPv6 IP addresses. + enum: + - slaac + - dhcpv6-stateful + - dhcpv6-stateless + type: string + raMode: + description: |- + raMode specifies the IPv6 router advertisement mode. It specifies whether + the networking service should transmit ICMPv6 packets. + enum: + - slaac + - dhcpv6-stateful + - dhcpv6-stateless + type: string + type: object + x-kubernetes-validations: + - message: ipv6 is immutable + rule: self == oldSelf name: - description: |- - name indicates the name of service. If not specified, the name of the ORC - resource will be used. + description: name is a human-readable name of the subnet. If not + set, the object's name will be used. maxLength: 255 minLength: 1 pattern: ^[^,]+$ type: string - type: - description: type indicates which resource the service is responsible - for. - maxLength: 255 + networkRef: + description: networkRef is a reference to the ORC Network which + this subnet is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: networkRef is immutable + rule: self == oldSelf + projectRef: + description: |- + projectRef is a reference to the ORC Project this resource is associated with. + Typically, only used by admin. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: projectRef is immutable + rule: self == oldSelf + routerRef: + description: routerRef specifies a router to attach the subnet + to + maxLength: 253 minLength: 1 type: string + x-kubernetes-validations: + - message: routerRef is immutable + rule: self == oldSelf + tags: + description: tags is a list of tags which will be applied to the + subnet. + 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: - - type + - cidr + - ipVersion + - networkRef type: object required: - cloudCredentialsRef @@ -6640,30 +8537,142 @@ 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: + allocationPools: + description: |- + allocationPools is a list of sub-ranges within CIDR available for dynamic + allocation to ports. + items: + properties: + end: + description: end is the last IP address in the allocation + pool. + maxLength: 1024 + type: string + start: + description: start is the first IP address in the allocation + pool. + maxLength: 1024 + type: string + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic + cidr: + description: cidr representing IP range for this subnet, based + on IP version. + maxLength: 1024 + type: string + createdAt: + description: createdAt shows the date and time when the resource + was created. The date and time stamp format is ISO 8601 + format: date-time + type: string description: - description: description indicates the description of service. - maxLength: 255 + description: description is a human-readable description for the + resource. + maxLength: 1024 type: string - enabled: - description: enabled indicates whether the service is enabled - or not. + dnsNameservers: + description: dnsNameservers is a list of name servers used by + hosts in this subnet. + items: + maxLength: 1024 + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: atomic + dnsPublishFixedIP: + description: dnsPublishFixedIP specifies whether the fixed IP + addresses are published to the DNS. + type: boolean + enableDHCP: + description: enableDHCP specifies whether DHCP is enabled for + this subnet or not. type: boolean + gatewayIP: + description: gatewayIP is the default gateway used by devices + in this subnet, if any. + maxLength: 1024 + type: string + hostRoutes: + description: |- + hostRoutes is a list of routes that should be used by devices with IPs + from this subnet (not including local subnet route). + items: + properties: + destination: + description: destination for the additional route. + maxLength: 1024 + type: string + nextHop: + description: nextHop for the additional route. + maxLength: 1024 + type: string + type: object + maxItems: 256 + type: array + x-kubernetes-list-type: atomic + ipVersion: + description: ipVersion specifies IP version, either `4' or `6'. + format: int32 + type: integer + ipv6AddressMode: + description: ipv6AddressMode specifies mechanisms for assigning + IPv6 IP addresses. + maxLength: 1024 + type: string + ipv6RAMode: + description: |- + ipv6RAMode is the IPv6 router advertisement mode. It specifies + whether the networking service should transmit ICMPv6 packets. + maxLength: 1024 + type: string name: - description: name indicates the name of service. - maxLength: 255 + description: name is the human-readable name of the subnet. Might + not be unique. + maxLength: 1024 type: string - type: - description: type indicates which resource the service is responsible - for. - maxLength: 255 + networkID: + description: networkID is the ID of the network to which the subnet + belongs. + maxLength: 1024 + type: string + projectID: + description: projectID is the project owner of the subnet. + maxLength: 1024 + type: string + revisionNumber: + description: revisionNumber optionally set via extensions/standard-attr-revisions + format: int64 + type: integer + subnetPoolID: + description: subnetPoolID is the id of the subnet pool associated + with the subnet. + maxLength: 1024 + type: string + tags: + description: tags optionally set via extensions/attributestags + items: + maxLength: 1024 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + updatedAt: + description: updatedAt shows the date and time when the resource + was updated. The date and time stamp format is ISO 8601 + format: date-time type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -6674,17 +8683,17 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.1 - name: subnets.openstack.k-orc.cloud + controller-gen.kubebuilder.io/version: v0.20.1 + name: trunks.openstack.k-orc.cloud spec: group: openstack.k-orc.cloud names: categories: - openstack - kind: Subnet - listKind: SubnetList - plural: subnets - singular: subnet + kind: Trunk + listKind: TrunkList + plural: trunks + singular: trunk scope: Namespaced versions: - additionalPrinterColumns: @@ -6703,7 +8712,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Subnet is the Schema for an ORC resource. + description: Trunk is the Schema for an ORC resource. properties: apiVersion: description: |- @@ -6744,82 +8753,39 @@ spec: 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: - cidr: - description: cidr of the existing resource - format: cidr - maxLength: 49 - minLength: 1 - type: string - description: - description: description of the existing resource - maxLength: 255 - minLength: 1 - type: string - gatewayIP: - description: gatewayIP is the IP address of the gateway of - the existing resource - maxLength: 45 - minLength: 1 - type: string - ipVersion: - description: ipVersion of the existing resource - enum: - - 4 - - 6 - format: int32 - type: integer - ipv6: - description: ipv6 options of the existing resource - minProperties: 1 - properties: - addressMode: - description: addressMode specifies mechanisms for assigning - IPv6 IP addresses. - enum: - - slaac - - dhcpv6-stateful - - dhcpv6-stateless - type: string - raMode: - description: |- - raMode specifies the IPv6 router advertisement mode. It specifies whether - the networking service should transmit ICMPv6 packets. - enum: - - slaac - - dhcpv6-stateful - - dhcpv6-stateless - type: string - type: object + - 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 - networkRef: - description: networkRef is a reference to the ORC Network - which this subnet is associated with. - maxLength: 253 - minLength: 1 - type: string notTags: description: |- notTags is a list of tags to filter by. If specified, resources which @@ -6848,10 +8814,15 @@ spec: 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 this resource is associated with. - Typically, only used by admin. + description: projectRef is a reference to the ORC Project + which this resource is associated with. maxLength: 253 minLength: 1 type: string @@ -6891,6 +8862,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: @@ -6908,234 +8880,510 @@ spec: - 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: - allocationPools: - description: |- - allocationPools are IP Address pools that will be available for DHCP. IP - addresses must be in CIDR. + 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. + maxLength: 1024 + 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: - end: - description: end is the last IP address in the allocation - pool. - maxLength: 45 - minLength: 1 + portID: + description: portID is the OpenStack ID of the Port attached + as a subport. + maxLength: 1024 type: string - start: - description: start is the first IP address in the allocation - pool. - maxLength: 45 - minLength: 1 + 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 - required: - - end - - start type: object - maxItems: 32 + maxItems: 1024 type: array x-kubernetes-list-type: atomic - cidr: - description: cidr is the address CIDR of the subnet. It must match - the IP version specified in IPVersion. - format: cidr - maxLength: 49 - minLength: 1 - type: string - x-kubernetes-validations: - - message: cidr is immutable - rule: self == oldSelf - description: - description: description is a human-readable description for the - resource. - maxLength: 255 - minLength: 1 - type: string - dnsNameservers: - description: dnsNameservers are the nameservers to be set via - DHCP. + tags: + description: tags is the list of tags on the resource. items: - maxLength: 45 - minLength: 1 + maxLength: 1024 type: string - maxItems: 16 + maxItems: 64 type: array - x-kubernetes-list-type: set - dnsPublishFixedIP: + 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 + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.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: |- - dnsPublishFixedIP will either enable or disable the publication of - fixed IPs to the DNS. Defaults to false. - type: boolean - x-kubernetes-validations: - - message: dnsPublishFixedIP is immutable - rule: self == oldSelf - enableDHCP: - description: enableDHCP will either enable to disable the DHCP - service. - type: boolean - gateway: + 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: |- - 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. - properties: - ip: - description: |- - 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: string - type: - description: |- - 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 - type: string - required: - - type - type: object - hostRoutes: - description: hostRoutes are any static host routes to be set via - DHCP. - items: - properties: - destination: - description: destination for the additional route. - format: cidr - maxLength: 49 - minLength: 1 - type: string - nextHop: - description: nextHop for the additional route. - maxLength: 45 - minLength: 1 - type: string - required: - - destination - - nextHop - type: object - maxItems: 256 - type: array - x-kubernetes-list-type: atomic - ipVersion: - description: ipVersion is the IP version for the subnet. - enum: - - 4 - - 6 - format: int32 - type: integer - x-kubernetes-validations: - - message: ipVersion is immutable - rule: self == oldSelf - ipv6: - description: ipv6 contains IPv6-specific options. It may only - be set if IPVersion is 6. - minProperties: 1 - properties: - addressMode: - description: addressMode specifies mechanisms for assigning - IPv6 IP addresses. - enum: - - slaac - - dhcpv6-stateful - - dhcpv6-stateless + 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 - raMode: - description: |- - raMode specifies the IPv6 router advertisement mode. It specifies whether - the networking service should transmit ICMPv6 packets. - enum: - - slaac - - dhcpv6-stateful - - dhcpv6-stateless + 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: ipv6 is immutable + - message: defaultProjectRef is immutable rule: self == oldSelf - name: - description: name is a human-readable name of the subnet. If not - set, the object's name will be used. + description: + description: description is a human-readable description for the + resource. maxLength: 255 minLength: 1 - pattern: ^[^,]+$ type: string - networkRef: - description: networkRef is a reference to the ORC Network which - this subnet is associated with. + 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: networkRef is immutable + - message: domainRef is immutable rule: self == oldSelf - projectRef: + enabled: + description: enabled defines whether a user is enabled or disabled + type: boolean + name: description: |- - projectRef is a reference to the ORC Project this resource is associated with. - Typically, only used by admin. - maxLength: 253 + 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 - x-kubernetes-validations: - - message: projectRef is immutable - rule: self == oldSelf - routerRef: - description: routerRef specifies a router to attach the subnet - to + passwordRef: + 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 - x-kubernetes-validations: - - message: routerRef is immutable - rule: self == oldSelf - tags: - description: tags is a list of tags which will be applied to the - subnet. - 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: - - cidr - - ipVersion - - networkRef type: object + x-kubernetes-validations: + - message: passwordRef may not be removed once set + rule: '!has(oldSelf.passwordRef) || has(self.passwordRef)' required: - cloudCredentialsRef type: object @@ -7233,139 +9481,50 @@ 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: - allocationPools: + appliedPasswordRef: description: |- - allocationPools is a list of sub-ranges within CIDR available for dynamic - allocation to ports. - items: - properties: - end: - description: end is the last IP address in the allocation - pool. - maxLength: 1024 - type: string - start: - description: start is the first IP address in the allocation - pool. - maxLength: 1024 - type: string - type: object - maxItems: 32 - type: array - x-kubernetes-list-type: atomic - cidr: - description: cidr representing IP range for this subnet, based - on IP version. + appliedPasswordRef is the name of the Secret containing the + password that was last applied to the OpenStack resource. maxLength: 1024 type: string - createdAt: - description: createdAt shows the date and time when the resource - was created. The date and time stamp format is ISO 8601 - format: date-time + 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 - dnsNameservers: - description: dnsNameservers is a list of name servers used by - hosts in this subnet. - items: - maxLength: 1024 - type: string - maxItems: 16 - type: array - x-kubernetes-list-type: atomic - dnsPublishFixedIP: - description: dnsPublishFixedIP specifies whether the fixed IP - addresses are published to the DNS. - type: boolean - enableDHCP: - description: enableDHCP specifies whether DHCP is enabled for - this subnet or not. - type: boolean - gatewayIP: - description: gatewayIP is the default gateway used by devices - in this subnet, if any. - maxLength: 1024 - type: string - hostRoutes: - description: |- - hostRoutes is a list of routes that should be used by devices with IPs - from this subnet (not including local subnet route). - items: - properties: - destination: - description: destination for the additional route. - maxLength: 1024 - type: string - nextHop: - description: nextHop for the additional route. - maxLength: 1024 - type: string - type: object - maxItems: 256 - type: array - x-kubernetes-list-type: atomic - ipVersion: - description: ipVersion specifies IP version, either `4' or `6'. - format: int32 - type: integer - ipv6AddressMode: - description: ipv6AddressMode specifies mechanisms for assigning - IPv6 IP addresses. - maxLength: 1024 - type: string - ipv6RAMode: - description: |- - ipv6RAMode is the IPv6 router advertisement mode. It specifies - whether the networking service should transmit ICMPv6 packets. + 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 the human-readable name of the subnet. Might + description: name is a Human-readable name for the resource. Might not be unique. maxLength: 1024 type: string - networkID: - description: networkID is the ID of the network to which the subnet - belongs. - maxLength: 1024 - type: string - projectID: - description: projectID is the project owner of the subnet. - maxLength: 1024 - type: string - revisionNumber: - description: revisionNumber optionally set via extensions/standard-attr-revisions - format: int64 - type: integer - subnetPoolID: - description: subnetPoolID is the id of the subnet pool associated - with the subnet. + passwordExpiresAt: + description: passwordExpiresAt is the timestamp at which the user's + password expires. maxLength: 1024 type: string - tags: - description: tags optionally set via extensions/attributestags - items: - maxLength: 1024 - type: string - maxItems: 64 - type: array - x-kubernetes-list-type: atomic - updatedAt: - description: updatedAt shows the date and time when the resource - was updated. The date and time stamp format is ISO 8601 - format: date-time - type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -7376,7 +9535,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 @@ -7492,6 +9651,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: @@ -7546,6 +9706,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. @@ -7696,6 +9867,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 @@ -7762,6 +9934,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. @@ -7829,6 +10006,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -7839,7 +10018,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 @@ -7949,6 +10128,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: @@ -8124,6 +10304,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 @@ -8161,6 +10342,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true @@ -8285,7 +10468,10 @@ rules: - apiGroups: - openstack.k-orc.cloud resources: + - addressscopes + - applicationcredentials - domains + - endpoints - flavors - floatingips - groups @@ -8302,6 +10488,8 @@ rules: - servers - services - subnets + - trunks + - users - volumes - volumetypes verbs: @@ -8315,7 +10503,10 @@ rules: - apiGroups: - openstack.k-orc.cloud resources: + - addressscopes/status + - applicationcredentials/status - domains/status + - endpoints/status - flavors/status - floatingips/status - groups/status @@ -8332,6 +10523,8 @@ rules: - servers/status - services/status - subnets/status + - trunks/status + - users/status - volumes/status - volumetypes/status verbs: @@ -8459,7 +10652,7 @@ spec: - --health-probe-bind-address=:8081 command: - /manager - image: quay.io/orc/openstack-resource-controller:v2.4.0 + image: quay.io/orc/openstack-resource-controller:v2.5.0 livenessProbe: httpGet: path: /healthz From 5c24c4c4eb9172c9b9591e46e47d5c8d3fee191b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 11 Jun 2025 07:56:48 +0200 Subject: [PATCH 148/237] Port: fix type for SecurityGroupRefs It should be a list of KubernetesNameRef and not OpenStackName. Fixes #438 --- api/v1alpha1/port_types.go | 4 ++-- api/v1alpha1/zz_generated.deepcopy.go | 2 +- cmd/models-schema/zz_generated.openapi.go | 2 +- .../bases/openstack.k-orc.cloud_ports.yaml | 5 ++--- internal/controllers/port/actuator_test.go | 20 +++++++++---------- .../api/v1alpha1/portresourcespec.go | 4 ++-- test/apivalidations/port_test.go | 2 +- website/docs/crd-reference.md | 2 +- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 884411a07..2592082fe 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -162,12 +162,12 @@ type PortResourceSpec struct { // +optional AdminStateUp *bool `json:"adminStateUp,omitempty"` - // securityGroupRefs are the names of the security groups associated + // securityGroupRefs are references to the security groups associated // with this port. // +kubebuilder:validation:MaxItems:=64 // +listType=set // +optional - SecurityGroupRefs []OpenStackName `json:"securityGroupRefs,omitempty"` //nolint:kubeapilinter // https://github.com/k-orc/openstack-resource-controller/issues/438 + SecurityGroupRefs []KubernetesNameRef `json:"securityGroupRefs,omitempty"` // vnicType specifies the type of vNIC which this port should be // attached to. This is used to determine which mechanism driver(s) to diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ec4a2eb46..fdc2aa079 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3321,7 +3321,7 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { } if in.SecurityGroupRefs != nil { in, out := &in.SecurityGroupRefs, &out.SecurityGroupRefs - *out = make([]OpenStackName, len(*in)) + *out = make([]KubernetesNameRef, len(*in)) copy(*out, *in) } if in.ProjectRef != nil { diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 2e346c070..3ca9cbb8d 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -6223,7 +6223,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, }, SchemaProps: spec.SchemaProps{ - Description: "securityGroupRefs are the names of the security groups associated with this port.", + Description: "securityGroupRefs are references to the security groups associated with this port.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index e409abdaa..9018183f8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -379,12 +379,11 @@ spec: rule: self == oldSelf securityGroupRefs: description: |- - securityGroupRefs are the names of the security groups associated + securityGroupRefs are references to the security groups associated with this port. items: - maxLength: 255 + maxLength: 253 minLength: 1 - pattern: ^[^,]+$ type: string maxItems: 64 type: array diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index 81a2a7cc6..1da3d948e 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -234,8 +234,8 @@ func makeSecGroupWithID(id string) *orcv1alpha1.SecurityGroup { } func TestHandleSecurityGroupRefsUpdate(t *testing.T) { - sgWebName := orcv1alpha1.OpenStackName("sg-web") - sgDbName := orcv1alpha1.OpenStackName("sg-db") + sgWebName := orcv1alpha1.KubernetesNameRef("sg-web") + sgDbName := orcv1alpha1.KubernetesNameRef("sg-db") idWeb := "d564a44b-346c-4f71-92b1-5899b8979374" idDb := "1d23d83b-2a78-4c12-9e55-0a6e026dd201" @@ -243,14 +243,14 @@ func TestHandleSecurityGroupRefsUpdate(t *testing.T) { testCases := []struct { name string - newValue []orcv1alpha1.OpenStackName + newValue []orcv1alpha1.KubernetesNameRef existingValue []string secGroupMap map[string]*orcv1alpha1.SecurityGroup expectChange bool }{ { name: "Identical", - newValue: []orcv1alpha1.OpenStackName{sgWebName, sgDbName}, + newValue: []orcv1alpha1.KubernetesNameRef{sgWebName, sgDbName}, existingValue: []string{idWeb, idDb}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{ string(sgWebName): makeSecGroupWithID(idWeb), @@ -260,7 +260,7 @@ func TestHandleSecurityGroupRefsUpdate(t *testing.T) { }, { name: "Identical but different order", - newValue: []orcv1alpha1.OpenStackName{sgDbName, sgWebName}, + newValue: []orcv1alpha1.KubernetesNameRef{sgDbName, sgWebName}, existingValue: []string{idWeb, idDb}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{ string(sgWebName): makeSecGroupWithID(idWeb), @@ -270,7 +270,7 @@ func TestHandleSecurityGroupRefsUpdate(t *testing.T) { }, { name: "Add a security group", - newValue: []orcv1alpha1.OpenStackName{sgWebName, sgDbName}, + newValue: []orcv1alpha1.KubernetesNameRef{sgWebName, sgDbName}, existingValue: []string{idWeb}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{ string(sgWebName): makeSecGroupWithID(idWeb), @@ -280,7 +280,7 @@ func TestHandleSecurityGroupRefsUpdate(t *testing.T) { }, { name: "Remove a security group", - newValue: []orcv1alpha1.OpenStackName{sgWebName}, + newValue: []orcv1alpha1.KubernetesNameRef{sgWebName}, existingValue: []string{idWeb, idDb}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{ string(sgWebName): makeSecGroupWithID(idWeb), @@ -290,7 +290,7 @@ func TestHandleSecurityGroupRefsUpdate(t *testing.T) { }, { name: "Replace a security group", - newValue: []orcv1alpha1.OpenStackName{sgWebName, sgDbName}, + newValue: []orcv1alpha1.KubernetesNameRef{sgWebName, sgDbName}, existingValue: []string{idWeb, idOther}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{ string(sgWebName): makeSecGroupWithID(idWeb), @@ -300,14 +300,14 @@ func TestHandleSecurityGroupRefsUpdate(t *testing.T) { }, { name: "Remove all security groups", - newValue: []orcv1alpha1.OpenStackName{}, + newValue: []orcv1alpha1.KubernetesNameRef{}, existingValue: []string{idWeb, idDb}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{}, expectChange: true, }, { name: "Add to empty list", - newValue: []orcv1alpha1.OpenStackName{sgWebName}, + newValue: []orcv1alpha1.KubernetesNameRef{sgWebName}, existingValue: []string{}, secGroupMap: map[string]*orcv1alpha1.SecurityGroup{ string(sgWebName): makeSecGroupWithID(idWeb), diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index 491a5c818..b1c4a2955 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -32,7 +32,7 @@ type PortResourceSpecApplyConfiguration struct { AllowedAddressPairs []AllowedAddressPairApplyConfiguration `json:"allowedAddressPairs,omitempty"` Addresses []AddressApplyConfiguration `json:"addresses,omitempty"` AdminStateUp *bool `json:"adminStateUp,omitempty"` - SecurityGroupRefs []apiv1alpha1.OpenStackName `json:"securityGroupRefs,omitempty"` + SecurityGroupRefs []apiv1alpha1.KubernetesNameRef `json:"securityGroupRefs,omitempty"` VNICType *string `json:"vnicType,omitempty"` PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` @@ -117,7 +117,7 @@ func (b *PortResourceSpecApplyConfiguration) WithAdminStateUp(value bool) *PortR // WithSecurityGroupRefs adds the given value to the SecurityGroupRefs 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 SecurityGroupRefs field. -func (b *PortResourceSpecApplyConfiguration) WithSecurityGroupRefs(values ...apiv1alpha1.OpenStackName) *PortResourceSpecApplyConfiguration { +func (b *PortResourceSpecApplyConfiguration) WithSecurityGroupRefs(values ...apiv1alpha1.KubernetesNameRef) *PortResourceSpecApplyConfiguration { for i := range values { b.SecurityGroupRefs = append(b.SecurityGroupRefs, values[i]) } diff --git a/test/apivalidations/port_test.go b/test/apivalidations/port_test.go index 6c963f4dd..4d84531c5 100644 --- a/test/apivalidations/port_test.go +++ b/test/apivalidations/port_test.go @@ -107,7 +107,7 @@ var _ = Describe("ORC Port API validations", func() { WithSecurityGroupRefs("sg-foo"). WithPortSecurity(orcv1alpha1.PortSecurityEnabled)) Expect(applyObj(ctx, port, patch)).To(Succeed()) - Expect(port.Spec.Resource.SecurityGroupRefs).To(Equal([]orcv1alpha1.OpenStackName{"sg-foo"})) + Expect(port.Spec.Resource.SecurityGroupRefs).To(Equal([]orcv1alpha1.KubernetesNameRef{"sg-foo"})) Expect(port.Spec.Resource.PortSecurity).To(Equal(orcv1alpha1.PortSecurityEnabled)) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 3db974f26..caf1705ca 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2730,7 +2730,7 @@ _Appears in:_ | `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: \{\}
| +| `securityGroupRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | securityGroupRefs are references to the security groups associated
with this port. | | MaxItems: 64
MaxLength: 253
MinLength: 1
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: \{\}
| From 43e9339eea243d804214f43a9400803f496b985d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 09:03:05 +0200 Subject: [PATCH 149/237] Add CI and GitHub configuration for release-2.0 branch Add backport workflow job, dependabot config, labels, auto-labeling rules, and weekly security scans for the new release-2.0 branch. --- .github/dependabot.yml | 41 ++++++++++++++ .github/labeler.yml | 2 + .github/labels.yaml | 6 +++ .github/workflows/backport.yaml | 59 +++++++++++++++++++++ .github/workflows/weekly-security-scan.yaml | 2 +- 5 files changed, 109 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 800e48911..7289db69c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -84,3 +84,44 @@ updates: - dependency-name: "k8s.io/*" update-types: ["version-update:semver-major", "version-update:semver-minor"] ## release-1.0 branch config ends here +## release-2.0 branch config starts here +# github-actions +- directory: "/" + package-ecosystem: "github-actions" + schedule: + interval: "weekly" + day: "monday" + cooldown: + default-days: 7 + target-branch: release-2.0 + groups: + all-github-actions: + patterns: [ "*" ] + commit-message: + prefix: ":seedling:" + include: scope +# Go directories +- directories: + - "/" + package-ecosystem: "gomod" + schedule: + interval: "weekly" + day: "monday" + cooldown: + default-days: 7 + target-branch: release-2.0 + groups: + all-go-mod-patch-and-minor: + patterns: [ "*" ] + update-types: [ "patch", "minor" ] + commit-message: + prefix: ":seedling:" + include: scope + ignore: + # Ignore controller-runtime major and minor bumps as its upgraded manually. + - dependency-name: "sigs.k8s.io/controller-runtime" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + # Ignore k8s major and minor bumps and its transitives modules + - dependency-name: "k8s.io/*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] +## release-2.0 branch config ends here diff --git a/.github/labeler.yml b/.github/labeler.yml index 88ebd919e..c7538490d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,2 +1,4 @@ v1.0: - base-branch: 'release-1.0' +v2.0: +- base-branch: 'release-2.0' diff --git a/.github/labels.yaml b/.github/labels.yaml index d53fa110a..7106d0e66 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -4,6 +4,12 @@ - color: '30ABB9' description: This PR targets v1.0 name: v1.0 +- color: 'D97706' + description: This PR will be backported to v2.0 + name: backport-v2.0 +- color: 'D97706' + description: This PR targets v2.0 + name: v2.0 - color: 'BCF611' description: A good issue for first-time contributors diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 1c8173a88..7ff5ec01e 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -69,3 +69,62 @@ jobs: NUMBER: ${{ github.event.pull_request.number }} BODY: > Labels `semver:major` and `semver:minor` block backports to the branch `release-1.0`. + + backport_v2_0: + name: "Backport to v2.0" + # Only react to merged PRs for security reasons. + # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. + if: > + github.event.pull_request.merged + && ( + github.event.action == 'closed' + && contains(github.event.pull_request.labels.*.name, 'backport-v2.0') + || ( + github.event.action == 'labeled' + && contains(github.event.label.name, 'backport-v2.0') + ) + ) + runs-on: ubuntu-latest + steps: + - name: Generate a token from the orc-backport-bot github-app + id: generate_token + uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # tag=v4.0.0 + with: + 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: > + contains(github.event.pull_request.labels.*.name, 'semver:patch') + || contains(github.event.pull_request.labels.*.name, 'semver:minor') + || contains(github.event.label.name, 'semver:patch') + || contains(github.event.label.name, 'semver:minor') + uses: kiegroup/git-backporting@08da0b07ef2330d189f6074ec8db736b3aa9f465 # tag=v4.9.1 + with: + target-branch: release-2.0 + pull-request: ${{ github.event.pull_request.url }} + auth: ${{ steps.generate_token.outputs.token }} + no-squash: true + strategy-option: find-renames + + - name: Report failure + if: failure() + run: gh issue comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + BODY: > + Failed to backport PR to `release-2.0` branch. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + + - name: Report an error if backport unsupported labels + if: > + contains(github.event.pull_request.labels.*.name, 'semver:major') + || contains(github.event.label.name, 'semver:major') + run: gh pr comment "$NUMBER" --body "$BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + BODY: > + Label `semver:major` blocks backports to the branch `release-2.0`. diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 92a733078..44c7239b9 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - branch: [main, release-1.0] + branch: [main, release-1.0, release-2.0] name: Trivy runs-on: ubuntu-latest steps: From c3ddfddd1d91662698534ed71a6a0c0949d3b31d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:08:34 +0000 Subject: [PATCH 150/237] :seedling:(deps): Bump golang.org/x/text Bumps the all-go-mod-patch-and-minor group with 1 update in the / directory: [golang.org/x/text](https://github.com/golang/text). Updates `golang.org/x/text` from 0.35.0 to 0.36.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.35.0...v0.36.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.36.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 | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a7f57b441..a2287aeda 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ 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.35.0 + golang.org/x/text v0.36.0 k8s.io/api v0.34.6 k8s.io/apimachinery v0.34.6 k8s.io/client-go v0.34.6 @@ -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.33.0 // indirect + golang.org/x/mod v0.34.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.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 v0.43.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-20251202230838-ff82c1b0f217 // indirect diff --git a/go.sum b/go.sum index 40912761b..ea0cf8879 100644 --- a/go.sum +++ b/go.sum @@ -207,8 +207,8 @@ 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.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= 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= @@ -231,16 +231,16 @@ 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= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 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.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= 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 f19469a8083d8b63150856d9e56287a6ebf23533 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:09:47 +0000 Subject: [PATCH 151/237] :seedling:(deps): Bump actions/upload-artifact Bumps the all-github-actions group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/upload-artifact` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/e2e.yaml | 2 +- .github/workflows/semver.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 457bb3814..223568713 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -68,7 +68,7 @@ jobs: - name: Upload logs artifacts on failure if: failure() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 with: name: e2e-${{ matrix.name }}-${{ github.run_id }} path: /tmp/artifacts/* diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 9ff27d751..5bd81168e 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -62,7 +62,7 @@ jobs: - name: Upload semver results if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # tag=v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 with: name: semver-results path: semver-results/ From cb2cd8ab6005715464896d36fced806b7c10dc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 11:20:17 +0200 Subject: [PATCH 152/237] Fix zizmor excessive-permissions warning in backport workflow Move permissions from workflow level to job level to follow the principle of least privilege. This resolves the zizmor excessive-permissions finding without changing the effective permissions of either job. --- .github/workflows/backport.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 7ff5ec01e..db1fbc855 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -6,13 +6,14 @@ on: types: - closed - labeled -permissions: - contents: read - pull-requests: write +permissions: {} jobs: backport_v1_0: name: "Backport to v1.0" + permissions: + contents: read + pull-requests: write # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > @@ -72,6 +73,9 @@ jobs: backport_v2_0: name: "Backport to v2.0" + permissions: + contents: read + pull-requests: write # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > From 6a9534276965d176fd1df7a2bc00ffb7b46c8a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:07 +0200 Subject: [PATCH 153/237] FloatingIP: Tighten adoption filter ListOSResourcesForAdoption only filtered by floating IP address and tags, without considering the floating network or project. Adoption could match a floating IP on the wrong network when concurrent tests created floating IPs with the same address on different external networks. Resolve FloatingNetworkRef or FloatingSubnetRef to obtain the OpenStack network ID and include it in the list filter. Also include ProjectID when ProjectRef is set. Collapse floatingipCreateActuator into floatingipActuator since the split is no longer needed now that k8sClient is on the base actuator. --- internal/controllers/floatingip/actuator.go | 84 ++++++++++++++------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/internal/controllers/floatingip/actuator.go b/internal/controllers/floatingip/actuator.go index 8a99c564e..9ea92da8e 100644 --- a/internal/controllers/floatingip/actuator.go +++ b/internal/controllers/floatingip/actuator.go @@ -46,15 +46,11 @@ type ( ) type floatingipActuator struct { - osClient osclients.NetworkClient -} - -type floatingipCreateActuator struct { - floatingipActuator + osClient osclients.NetworkClient k8sClient client.Client } -var _ createResourceActuator = floatingipCreateActuator{} +var _ createResourceActuator = floatingipActuator{} var _ deleteResourceActuator = floatingipActuator{} func (floatingipActuator) GetResourceID(osResource *osResourceT) string { @@ -70,22 +66,69 @@ func (actuator floatingipActuator) GetOSResourceByID(ctx context.Context, id str } func (actuator floatingipActuator) ListOSResourcesForAdoption(ctx context.Context, obj *orcv1alpha1.FloatingIP) (floatingipIterator, bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { return nil, false } // we only support adoption of floatingips by IP as they don't have name - if obj.Spec.Resource.FloatingIP == nil { + if resource.FloatingIP == nil { return nil, false } + // Resolve the floating network ID from either FloatingNetworkRef or + // FloatingSubnetRef. Exactly one of these must be set per API + // validation. Without the network ID, adoption could match a floating + // IP on the wrong network. + var floatingNetworkID string + if resource.FloatingNetworkRef != nil { + network, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.FloatingNetworkRef, "Network", + func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + floatingNetworkID = ptr.Deref(network.Status.ID, "") + } else if resource.FloatingSubnetRef != nil { + subnet, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.FloatingSubnetRef, "Subnet", + func(dep *orcv1alpha1.Subnet) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil && dep.Status.Resource != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + floatingNetworkID = subnet.Status.Resource.NetworkID + } + + // Resolve the project ID from ProjectRef if set. + var projectID string + if resource.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + listOpts := floatingips.ListOpts{ - FloatingIP: string(ptr.Deref(obj.Spec.Resource.FloatingIP, "")), - Tags: tags.Join(obj.Spec.Resource.Tags), + FloatingIP: string(ptr.Deref(resource.FloatingIP, "")), + FloatingNetworkID: floatingNetworkID, + ProjectID: projectID, + Tags: tags.Join(resource.Tags), } return actuator.osClient.ListFloatingIP(ctx, listOpts), true } -func (actuator floatingipCreateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { +func (actuator floatingipActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus network, rs := dependency.FetchDependency( @@ -132,7 +175,7 @@ func (actuator floatingipCreateActuator) ListOSResourcesForImport(ctx context.Co return actuator.osClient.ListFloatingIP(ctx, listOpts), nil } -func (actuator floatingipCreateActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.FloatingIP) (*osResourceT, progress.ReconcileStatus) { +func (actuator floatingipActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.FloatingIP) (*osResourceT, progress.ReconcileStatus) { resource := obj.Spec.Resource if resource == nil { // Should have been caught by API validation @@ -304,7 +347,7 @@ func (floatingipHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { } func (floatingipHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { - return newCreateActuator(ctx, orcObject, controller) + return newActuator(ctx, orcObject, controller) } func (floatingipHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { @@ -330,18 +373,7 @@ func newActuator(ctx context.Context, orcObject *orcv1alpha1.FloatingIP, control } return floatingipActuator{ - osClient: osClient, - }, nil -} - -func newCreateActuator(ctx context.Context, orcObject *orcv1alpha1.FloatingIP, controller interfaces.ResourceController) (floatingipCreateActuator, progress.ReconcileStatus) { - floatingipActuator, reconcileStatus := newActuator(ctx, orcObject, controller) - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { - return floatingipCreateActuator{}, reconcileStatus - } - - return floatingipCreateActuator{ - floatingipActuator: floatingipActuator, - k8sClient: controller.GetK8sClient(), + osClient: osClient, + k8sClient: controller.GetK8sClient(), }, nil } From 0fd870c48aa51add4ef0697dc697f242fe7813ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:13 +0200 Subject: [PATCH 154/237] Subnet: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve NetworkRef and include NetworkID, the immutable CIDR and IPVersion fields, and ProjectID when set in the list filter to prevent adopting a subnet on the wrong network or with the wrong addressing. --- internal/controllers/subnet/actuator.go | 39 +++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/internal/controllers/subnet/actuator.go b/internal/controllers/subnet/actuator.go index f3e0e8dd4..8a9012fb4 100644 --- a/internal/controllers/subnet/actuator.go +++ b/internal/controllers/subnet/actuator.go @@ -77,10 +77,45 @@ func (actuator subnetActuator) GetOSResourceByID(ctx context.Context, id string) } func (actuator subnetActuator) ListOSResourcesForAdoption(ctx context.Context, obj orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { return nil, false } - listOpts := subnets.ListOpts{Name: getResourceName(obj)} + + // Resolve the network ID from NetworkRef. Without the network ID, + // adoption could match a subnet on the wrong network. + network, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, &resource.NetworkRef, "Network", + func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + + // Resolve the project ID from ProjectRef if set. + var projectID string + if resource.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + + listOpts := subnets.ListOpts{ + Name: getResourceName(obj), + NetworkID: ptr.Deref(network.Status.ID, ""), + CIDR: string(resource.CIDR), + IPVersion: int(resource.IPVersion), + ProjectID: projectID, + } return actuator.osClient.ListSubnet(ctx, listOpts), true } From 053fc18ee87ed2660b7297a3a5f60b577cfa96ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:18 +0200 Subject: [PATCH 155/237] Port: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve NetworkRef and include NetworkID, MACAddress when specified (immutable, globally unique), and ProjectID when set in the list filter to prevent adopting a port on the wrong network. --- internal/controllers/port/actuator.go | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 363ae931d..71e3632b2 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -117,11 +117,44 @@ func (actuator portActuator) GetOSResourceByID(ctx context.Context, id string) ( } func (actuator portActuator) ListOSResourcesForAdoption(ctx context.Context, obj *orcv1alpha1.Port) (portIterator, bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { + return nil, false + } + + // Resolve the network ID from NetworkRef. Without the network ID, + // adoption could match a port on the wrong network. + network, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, &resource.NetworkRef, "Network", + func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { return nil, false } - listOpts := ports.ListOpts{Name: getResourceName(obj)} + // Resolve the project ID from ProjectRef if set. + var projectID string + if resource.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + + listOpts := ports.ListOpts{ + Name: getResourceName(obj), + NetworkID: ptr.Deref(network.Status.ID, ""), + MACAddress: resource.MACAddress, + ProjectID: projectID, + } return actuator.osClient.ListPort(ctx, listOpts), true } From 8c8067453bc41d9bc3c390eaff59c7d1c04aa8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:23 +0200 Subject: [PATCH 156/237] Trunk: Tighten adoption filter ListOSResourcesForAdoption only filtered by name and description. Resolve PortRef and include PortID, and ProjectID when set, in the list filter to prevent adopting a trunk associated with the wrong parent port. --- internal/controllers/trunk/actuator.go | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/internal/controllers/trunk/actuator.go b/internal/controllers/trunk/actuator.go index 86cd107fb..ff20fc03c 100644 --- a/internal/controllers/trunk/actuator.go +++ b/internal/controllers/trunk/actuator.go @@ -73,9 +73,38 @@ func (actuator trunkActuator) ListOSResourcesForAdoption(ctx context.Context, or return nil, false } + // Resolve the port ID from PortRef. Without the port ID, adoption + // could match a trunk associated with the wrong parent port. + port, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.PortRef, "Port", + func(dep *orcv1alpha1.Port) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + + // Resolve the project ID from ProjectRef if set. + var projectID string + if resourceSpec.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + listOpts := trunks.ListOpts{ Name: getResourceName(orcObject), Description: string(ptr.Deref(resourceSpec.Description, "")), + PortID: ptr.Deref(port.Status.ID, ""), + ProjectID: projectID, } return actuator.osClient.ListTrunks(ctx, listOpts), true From cbd09dd0b4b6a48b54e655a5d6068c4a4bef5ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:06:41 +0200 Subject: [PATCH 157/237] Project: Tighten adoption filter ListOSResourcesForAdoption only filtered by name and tags. Resolve DomainRef when set and include DomainID in the list filter to prevent adopting a project in the wrong domain. --- internal/controllers/project/actuator.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/controllers/project/actuator.go b/internal/controllers/project/actuator.go index ca156cee0..569847555 100644 --- a/internal/controllers/project/actuator.go +++ b/internal/controllers/project/actuator.go @@ -75,13 +75,31 @@ func (actuator projectActuator) GetOSResourceByID(ctx context.Context, id string } func (actuator projectActuator) ListOSResourcesForAdoption(ctx context.Context, obj orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { return nil, false } + // Resolve the domain ID from DomainRef if set. Without the domain + // ID, adoption could match a project in the wrong domain. + var domainID string + if resource.DomainRef != nil { + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + domainID = ptr.Deref(domain.Status.ID, "") + } + listOpts := projects.ListOpts{ - Name: getResourceName(obj), - Tags: tags.Join(obj.Spec.Resource.Tags), + Name: getResourceName(obj), + DomainID: domainID, + Tags: tags.Join(resource.Tags), } return actuator.osClient.ListProjects(ctx, listOpts), true From 1253074f6826bc1f423be237d78571b87605103d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:06:53 +0200 Subject: [PATCH 158/237] User: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve DomainRef when set and include DomainID in the list filter to prevent adopting a user in the wrong domain. --- internal/controllers/user/actuator.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index d82014cab..3637131c7 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -74,8 +74,25 @@ func (actuator userActuator) ListOSResourcesForAdoption(ctx context.Context, orc return nil, false } + // Resolve the domain ID from DomainRef if set. Without the domain + // ID, adoption could match a user in the wrong domain. + var domainID string + if resourceSpec.DomainRef != nil { + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + domainID = ptr.Deref(domain.Status.ID, "") + } + listOpts := users.ListOpts{ - Name: getResourceName(orcObject), + Name: getResourceName(orcObject), + DomainID: domainID, } return actuator.osClient.ListUsers(ctx, listOpts), true From 7fe8404d1d97614dbdf913e51dd8ebce0892fc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:07:07 +0200 Subject: [PATCH 159/237] Group: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve DomainRef when set and include DomainID in the list filter to prevent adopting a group in the wrong domain. --- internal/controllers/group/actuator.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/controllers/group/actuator.go b/internal/controllers/group/actuator.go index afdaad252..c0973273f 100644 --- a/internal/controllers/group/actuator.go +++ b/internal/controllers/group/actuator.go @@ -71,8 +71,25 @@ func (actuator groupActuator) ListOSResourcesForAdoption(ctx context.Context, or return nil, false } + // Resolve the domain ID from DomainRef if set. Without the domain + // ID, adoption could match a group in the wrong domain. + var domainID string + if resourceSpec.DomainRef != nil { + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + domainID = ptr.Deref(domain.Status.ID, "") + } + listOpts := groups.ListOpts{ - Name: getResourceName(orcObject), + Name: getResourceName(orcObject), + DomainID: domainID, } return actuator.osClient.ListGroups(ctx, listOpts), true From 71912ee54cd264df15ca36937284a8dcf73cf907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:40 +0200 Subject: [PATCH 160/237] AddressScope: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Include the immutable IPVersion field and ProjectID when set in the list filter to prevent adopting an address scope with the wrong IP version or from the wrong project. --- internal/controllers/addressscope/actuator.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/controllers/addressscope/actuator.go b/internal/controllers/addressscope/actuator.go index dd062fe9f..48ba5fac2 100644 --- a/internal/controllers/addressscope/actuator.go +++ b/internal/controllers/addressscope/actuator.go @@ -71,8 +71,25 @@ func (actuator addressscopeActuator) ListOSResourcesForAdoption(ctx context.Cont return nil, false } + // Resolve the project ID from ProjectRef if set. + var projectID string + if resourceSpec.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + listOpts := addressscopes.ListOpts{ - Name: getResourceName(orcObject), + Name: getResourceName(orcObject), + IPVersion: int(resourceSpec.IPVersion), + ProjectID: projectID, } return actuator.osClient.ListAddressScopes(ctx, listOpts), true From 95458333cc82f818c4429a748e47f1d6addea906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:45 +0200 Subject: [PATCH 161/237] SecurityGroup: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve ProjectRef when set and include ProjectID and the Stateful field in the list filter to prevent adopting a security group from the wrong project or with the wrong stateful/stateless behavior. --- .../controllers/securitygroup/actuator.go | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index 889722e4b..29c2af60c 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -77,11 +77,33 @@ func (actuator securityGroupActuator) GetOSResourceByID(ctx context.Context, id } func (actuator securityGroupActuator) ListOSResourcesForAdoption(ctx context.Context, obj *orcv1alpha1.SecurityGroup) (securityGroupIterator, bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { return nil, false } - listOpts := groups.ListOpts{Name: getResourceName(obj)} + // Resolve the project ID from ProjectRef if set. Without the project + // ID, adoption with admin-scoped credentials could match a security + // group in the wrong project. + var projectID string + if resource.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + + listOpts := groups.ListOpts{ + Name: getResourceName(obj), + ProjectID: projectID, + Stateful: resource.Stateful, + } return actuator.osClient.ListSecGroup(ctx, listOpts), true } From bfb881ab3798e4b2add39bbf397d3e54a8cbff97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:07:45 +0200 Subject: [PATCH 162/237] Network: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve ProjectRef when set and include ProjectID in the list filter to prevent adopting a network from the wrong project with admin-scoped credentials. --- internal/controllers/network/actuator.go | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/internal/controllers/network/actuator.go b/internal/controllers/network/actuator.go index ffb7a147b..a1f970456 100644 --- a/internal/controllers/network/actuator.go +++ b/internal/controllers/network/actuator.go @@ -72,11 +72,32 @@ func (actuator networkActuator) GetOSResourceByID(ctx context.Context, id string } func (actuator networkActuator) ListOSResourcesForAdoption(ctx context.Context, obj orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { return nil, false } - listOpts := networks.ListOpts{Name: getResourceName(obj)} + // Resolve the project ID from ProjectRef if set. Without the project + // ID, adoption with admin-scoped credentials could match a network + // in the wrong project. + var projectID string + if resource.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + + listOpts := networks.ListOpts{ + Name: getResourceName(obj), + ProjectID: projectID, + } return actuator.osClient.ListNetwork(ctx, listOpts), true } From 578eccc4bee92cc7f4893c9bd998fabc4c164cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:32:55 +0200 Subject: [PATCH 163/237] Router: Tighten adoption filter ListOSResourcesForAdoption only filtered by name. Resolve ProjectRef when set and include ProjectID and the immutable Distributed field in the list filter to prevent adopting a router from the wrong project or with the wrong topology. Collapse routerCreateActuator into routerActuator since the split is no longer needed now that k8sClient is on the base actuator. --- internal/controllers/router/actuator.go | 55 ++++++++++++++----------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/internal/controllers/router/actuator.go b/internal/controllers/router/actuator.go index 59768482e..762629c52 100644 --- a/internal/controllers/router/actuator.go +++ b/internal/controllers/router/actuator.go @@ -48,15 +48,11 @@ type ( ) type routerActuator struct { - osClient osclients.NetworkClient -} - -type routerCreateActuator struct { - routerActuator + osClient osclients.NetworkClient k8sClient client.Client } -var _ createResourceActuator = routerCreateActuator{} +var _ createResourceActuator = routerActuator{} var _ deleteResourceActuator = routerActuator{} func (routerActuator) GetResourceID(osResource *osResourceT) string { @@ -72,15 +68,37 @@ func (actuator routerActuator) GetOSResourceByID(ctx context.Context, id string) } func (actuator routerActuator) ListOSResourcesForAdoption(ctx context.Context, obj *orcv1alpha1.Router) (routerIterator, bool) { - if obj.Spec.Resource == nil { + resource := obj.Spec.Resource + if resource == nil { return nil, false } - listOpts := routers.ListOpts{Name: getResourceName(obj)} + // Resolve the project ID from ProjectRef if set. Without the project + // ID, adoption with admin-scoped credentials could match a router + // in the wrong project. + var projectID string + if resource.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, resource.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { + return nil, false + } + projectID = ptr.Deref(project.Status.ID, "") + } + + listOpts := routers.ListOpts{ + Name: getResourceName(obj), + ProjectID: projectID, + Distributed: resource.Distributed, + } return actuator.osClient.ListRouter(ctx, listOpts), true } -func (actuator routerCreateActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { +func (actuator routerActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus project, rs := dependency.FetchDependency( @@ -108,7 +126,7 @@ func (actuator routerCreateActuator) ListOSResourcesForImport(ctx context.Contex return actuator.osClient.ListRouter(ctx, listOpts), nil } -func (actuator routerCreateActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.Router) (*osResourceT, progress.ReconcileStatus) { +func (actuator routerActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.Router) (*osResourceT, progress.ReconcileStatus) { resource := obj.Spec.Resource if resource == nil { // Should have been caught by API validation @@ -274,7 +292,7 @@ func (routerHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { } func (routerHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { - return newCreateActuator(ctx, orcObject, controller) + return newActuator(ctx, orcObject, controller) } func (routerHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { @@ -300,18 +318,7 @@ func newActuator(ctx context.Context, orcObject *orcv1alpha1.Router, controller } return routerActuator{ - osClient: osClient, - }, nil -} - -func newCreateActuator(ctx context.Context, orcObject *orcv1alpha1.Router, controller interfaces.ResourceController) (routerCreateActuator, progress.ReconcileStatus) { - routerActuator, reconcileStatus := newActuator(ctx, orcObject, controller) - if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { - return routerCreateActuator{}, reconcileStatus - } - - return routerCreateActuator{ - routerActuator: routerActuator, - k8sClient: controller.GetK8sClient(), + osClient: osClient, + k8sClient: controller.GetK8sClient(), }, nil } From 8fb26f7d37a6989ebce7d8166184e5380705843e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 14:29:24 +0200 Subject: [PATCH 164/237] ServerGroup: Tighten adoption filter Include the immutable Policy field in the client-side adoption filter to prevent adopting a server group with the wrong scheduling policy (e.g. affinity vs anti-affinity). --- internal/controllers/servergroup/actuator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/controllers/servergroup/actuator.go b/internal/controllers/servergroup/actuator.go index bcb41b2d7..04dd07975 100644 --- a/internal/controllers/servergroup/actuator.go +++ b/internal/controllers/servergroup/actuator.go @@ -22,6 +22,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servergroups" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" @@ -75,7 +76,7 @@ func (actuator servergroupActuator) ListOSResourcesForAdoption(ctx context.Conte filters := []osclients.ResourceFilter[osResourceT]{ func(f *servergroups.ServerGroup) bool { name := getResourceName(orcObject) - return f.Name == name + return f.Name == name && ptr.Deref(f.Policy, "") == string(resourceSpec.Policy) }, } listOpts := servergroups.ListOpts{} From 5dfe1563abc0858d795186e2ade0fda886aa0b0d Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 31 Dec 2025 15:05:41 +0200 Subject: [PATCH 165/237] Add boot-from-volume support to server controller Add support for booting servers from Cinder volumes instead of images. This enables the boot-from-volume (BFV) pattern where a bootable volume (created from an image) is used as the root disk. Design decisions: 1. Boot volume vs data volumes separation: - Only the boot volume (bootVolume field) is attached at server creation time via Nova's block device mapping - Additional data volumes continue to use the existing dynamic attachment mechanism (spec.resource.volumes) which attaches volumes after server creation - This separation allows data volumes to remain mutable (add/remove after server creation) while the boot volume is immutable - Avoids duplicating volume attachment logic between creation-time and runtime mechanisms 2. No deleteOnTermination option: - Deliberately not exposing Nova's delete_on_termination flag - If enabled, Nova would delete the underlying OpenStack volume when the server is deleted, but the ORC Volume resource would remain as an orphan - The orphaned Volume resource would then attempt to recreate the volume, leading to unexpected behavior - Users who want the volume deleted should delete both Server and Volume resources, maintaining consistent ORC resource lifecycle management API Changes: - Add ServerBootVolumeSpec type with volumeRef and optional tag fields - Add bootVolume field to ServerResourceSpec (mutually exclusive with imageRef) - Make imageRef optional (pointer) with CEL validation Controller Changes: - Add bootVolumeDependency with deletion guard and unique controller name - Handle boot-from-volume in CreateResource by building BlockDevice list Tests & Examples: - Add kuttl test for server boot-from-volume scenario - Add config/samples/openstack_v1alpha1_server_boot_from_volume.yaml - Add examples/bases/boot-from-volume/ with volume and server examples assisted-by: claude --- api/v1alpha1/server_types.go | 29 ++++++++- api/v1alpha1/zz_generated.deepcopy.go | 30 +++++++++ cmd/models-schema/zz_generated.openapi.go | 41 +++++++++++- .../bases/openstack.k-orc.cloud_servers.yaml | 31 ++++++++- ...tack_v1alpha1_server_boot_from_volume.yaml | 25 ++++++++ .../bases/boot-from-volume/kustomization.yaml | 6 ++ examples/bases/boot-from-volume/server.yaml | 18 ++++++ examples/bases/boot-from-volume/volume.yaml | 14 +++++ internal/controllers/server/actuator.go | 39 ++++++++++-- internal/controllers/server/controller.go | 30 ++++++++- .../server-boot-from-volume/00-assert.yaml | 61 ++++++++++++++++++ .../00-create-resource.yaml | 30 +++++++++ .../00-prerequisites.yaml | 63 +++++++++++++++++++ .../tests/server-boot-from-volume/README.md | 14 +++++ .../api/v1alpha1/serverbootvolumespec.go | 52 +++++++++++++++ .../api/v1alpha1/serverresourcespec.go | 33 ++++++---- .../applyconfiguration/internal/internal.go | 12 ++++ pkg/clients/applyconfiguration/utils.go | 2 + website/docs/crd-reference.md | 22 ++++++- 19 files changed, 525 insertions(+), 27 deletions(-) create mode 100644 config/samples/openstack_v1alpha1_server_boot_from_volume.yaml create mode 100644 examples/bases/boot-from-volume/kustomization.yaml create mode 100644 examples/bases/boot-from-volume/server.yaml create mode 100644 examples/bases/boot-from-volume/volume.yaml create mode 100644 internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml create mode 100644 internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml create mode 100644 internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml create mode 100644 internal/controllers/server/tests/server-boot-from-volume/README.md create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.go diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go index f4f40b279..381cabb25 100644 --- a/api/v1alpha1/server_types.go +++ b/api/v1alpha1/server_types.go @@ -60,6 +60,20 @@ type ServerPortSpec struct { PortRef *KubernetesNameRef `json:"portRef,omitempty"` } +// ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation. +// When specified, the server boots from this volume instead of an image. +type ServerBootVolumeSpec struct { + // volumeRef is a reference to a Volume object. The volume must be + // bootable (created from an image) and available before server creation. + // +required + VolumeRef KubernetesNameRef `json:"volumeRef,omitempty"` + + // tag is the device tag applied to the volume. + // +kubebuilder:validation:MaxLength:=255 + // +optional + Tag *string `json:"tag,omitempty"` +} + // +kubebuilder:validation:MinProperties:=1 type ServerVolumeSpec struct { // volumeRef is a reference to a Volume object. Server creation will wait for @@ -122,6 +136,8 @@ type ServerInterfaceStatus struct { } // ServerResourceSpec contains the desired state of a server +// +kubebuilder:validation:XValidation:rule="has(self.imageRef) || has(self.bootVolume)",message="either imageRef or bootVolume must be specified" +// +kubebuilder:validation:XValidation:rule="!(has(self.imageRef) && has(self.bootVolume))",message="imageRef and bootVolume are mutually exclusive" type ServerResourceSpec struct { // name will be the name of the created resource. If not specified, the // name of the ORC object will be used. @@ -129,16 +145,23 @@ type ServerResourceSpec struct { Name *OpenStackName `json:"name,omitempty"` // imageRef references the image to use for the server instance. - // NOTE: This is not required in case of boot from volume. - // +required + // This field is required unless bootVolume is specified for boot-from-volume. + // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable" - ImageRef KubernetesNameRef `json:"imageRef,omitempty"` + ImageRef *KubernetesNameRef `json:"imageRef,omitempty"` // flavorRef references the flavor to use for the server instance. // +required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="flavorRef is immutable" FlavorRef KubernetesNameRef `json:"flavorRef,omitempty"` + // bootVolume specifies a volume to boot from instead of an image. + // When specified, imageRef must be omitted. The volume must be + // bootable (created from an image using imageRef in the Volume spec). + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="bootVolume is immutable" + BootVolume *ServerBootVolumeSpec `json:"bootVolume,omitempty"` + // 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. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fdc2aa079..6f5bf196d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -4649,6 +4649,26 @@ func (in *Server) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerBootVolumeSpec) DeepCopyInto(out *ServerBootVolumeSpec) { + *out = *in + if in.Tag != nil { + in, out := &in.Tag, &out.Tag + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerBootVolumeSpec. +func (in *ServerBootVolumeSpec) DeepCopy() *ServerBootVolumeSpec { + if in == nil { + return nil + } + out := new(ServerBootVolumeSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerFilter) DeepCopyInto(out *ServerFilter) { *out = *in @@ -5067,6 +5087,16 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) { *out = new(OpenStackName) **out = **in } + if in.ImageRef != nil { + in, out := &in.ImageRef, &out.ImageRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.BootVolume != nil { + in, out := &in.BootVolume, &out.BootVolume + *out = new(ServerBootVolumeSpec) + (*in).DeepCopyInto(*out) + } if in.UserData != nil { in, out := &in.UserData, &out.UserData *out = new(UserDataSpec) diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3ca9cbb8d..557911992 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -188,6 +188,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Server": schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerBootVolumeSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroup": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupFilter(ref), @@ -8843,6 +8844,34 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref common.Refe } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ServerBootVolumeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation. When specified, the server boots from this volume instead of an image.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeRef": { + SchemaProps: spec.SchemaProps{ + Description: "volumeRef is a reference to a Volume object. The volume must be bootable (created from an image) and available before server creation.", + Type: []string{"string"}, + Format: "", + }, + }, + "tag": { + SchemaProps: spec.SchemaProps{ + Description: "tag is the device tag applied to the volume.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"volumeRef"}, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -9579,7 +9608,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref }, "imageRef": { SchemaProps: spec.SchemaProps{ - Description: "imageRef references the image to use for the server instance. NOTE: This is not required in case of boot from volume.", + Description: "imageRef references the image to use for the server instance. This field is required unless bootVolume is specified for boot-from-volume.", Type: []string{"string"}, Format: "", }, @@ -9591,6 +9620,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref Format: "", }, }, + "bootVolume": { + SchemaProps: spec.SchemaProps{ + Description: "bootVolume specifies a volume to boot from instead of an image. When specified, imageRef must be omitted. The volume must be bootable (created from an image using imageRef in the Volume spec).", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec"), + }, + }, "userData": { SchemaProps: spec.SchemaProps{ Description: "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.", @@ -9703,11 +9738,11 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref }, }, }, - Required: []string{"imageRef", "flavorRef", "ports"}, + Required: []string{"flavorRef", "ports"}, }, }, Dependencies: []string{ - "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"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec", "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"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index be2b2c9bd..501e39ae2 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -203,6 +203,29 @@ spec: x-kubernetes-validations: - message: availabilityZone is immutable rule: self == oldSelf + bootVolume: + description: |- + bootVolume specifies a volume to boot from instead of an image. + When specified, imageRef must be omitted. The volume must be + bootable (created from an image using imageRef in the Volume spec). + properties: + tag: + description: tag is the device tag applied to the volume. + maxLength: 255 + type: string + volumeRef: + description: |- + volumeRef is a reference to a Volume object. The volume must be + bootable (created from an image) and available before server creation. + maxLength: 253 + minLength: 1 + type: string + required: + - volumeRef + type: object + x-kubernetes-validations: + - message: bootVolume is immutable + rule: self == oldSelf configDrive: description: |- configDrive specifies whether to attach a config drive to the server. @@ -224,7 +247,7 @@ spec: imageRef: description: |- imageRef references the image to use for the server instance. - NOTE: This is not required in case of boot from volume. + This field is required unless bootVolume is specified for boot-from-volume. maxLength: 253 minLength: 1 type: string @@ -355,9 +378,13 @@ spec: x-kubernetes-list-type: atomic required: - flavorRef - - imageRef - ports type: object + x-kubernetes-validations: + - message: either imageRef or bootVolume must be specified + rule: has(self.imageRef) || has(self.bootVolume) + - message: imageRef and bootVolume are mutually exclusive + rule: '!(has(self.imageRef) && has(self.bootVolume))' required: - cloudCredentialsRef type: object diff --git a/config/samples/openstack_v1alpha1_server_boot_from_volume.yaml b/config/samples/openstack_v1alpha1_server_boot_from_volume.yaml new file mode 100644 index 000000000..a63d01abc --- /dev/null +++ b/config/samples/openstack_v1alpha1_server_boot_from_volume.yaml @@ -0,0 +1,25 @@ +# Example of creating a server that boots from a Cinder volume instead of an image. +# This is the boot-from-volume (BFV) pattern. +# +# Prerequisites: +# - A bootable volume created from an image (see openstack_v1alpha1_volume_bootable.yaml) +# - Network, subnet, and port resources +# - A flavor +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Server +metadata: + name: server-boot-from-volume-sample +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + # Note: No imageRef - booting from volume instead + bootVolume: + volumeRef: bootable-volume-sample + flavorRef: server-sample + ports: + - portRef: server-sample + availabilityZone: nova diff --git a/examples/bases/boot-from-volume/kustomization.yaml b/examples/bases/boot-from-volume/kustomization.yaml new file mode 100644 index 000000000..ce2cb1adc --- /dev/null +++ b/examples/bases/boot-from-volume/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- volume.yaml +- server.yaml diff --git a/examples/bases/boot-from-volume/server.yaml b/examples/bases/boot-from-volume/server.yaml new file mode 100644 index 000000000..129203407 --- /dev/null +++ b/examples/bases/boot-from-volume/server.yaml @@ -0,0 +1,18 @@ +--- +# Server that boots from a volume instead of an image +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Server +metadata: + name: server +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: cloud-config + managementPolicy: managed + resource: + # No imageRef - booting from volume + bootVolume: + volumeRef: boot-volume + flavorRef: flavor + ports: + - portRef: port diff --git a/examples/bases/boot-from-volume/volume.yaml b/examples/bases/boot-from-volume/volume.yaml new file mode 100644 index 000000000..007353870 --- /dev/null +++ b/examples/bases/boot-from-volume/volume.yaml @@ -0,0 +1,14 @@ +--- +# Bootable volume created from the cirros image +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Volume +metadata: + name: boot-volume +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: cloud-config + managementPolicy: managed + resource: + size: 1 + imageRef: cirros diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index a044503f6..c89496429 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -160,15 +160,45 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp reconcileStatus := progress.NewReconcileStatus() - var image *orcv1alpha1.Image - { + // Determine if we're booting from volume or image + bootFromVolume := resource.BootVolume != nil + + var imageID string + if !bootFromVolume { + // Traditional boot from image dep, imageReconcileStatus := imageDependency.GetDependency( ctx, actuator.k8sClient, obj, func(image *orcv1alpha1.Image) bool { return orcv1alpha1.IsAvailable(image) && image.Status.ID != nil }, ) reconcileStatus = reconcileStatus.WithReconcileStatus(imageReconcileStatus) - image = dep + if dep != nil && dep.Status.ID != nil { + imageID = *dep.Status.ID + } + } + + // Resolve boot volume for boot-from-volume + var blockDevices []servers.BlockDevice + if bootFromVolume { + bootVolume, bvReconcileStatus := bootVolumeDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(volume *orcv1alpha1.Volume) bool { + return orcv1alpha1.IsAvailable(volume) && volume.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(bvReconcileStatus) + + if bootVolume != nil && bootVolume.Status.ID != nil { + bd := servers.BlockDevice{ + SourceType: servers.SourceVolume, + DestinationType: servers.DestinationVolume, + UUID: *bootVolume.Status.ID, + BootIndex: 0, // Always 0 for boot volume + } + if resource.BootVolume.Tag != nil { + bd.Tag = *resource.BootVolume.Tag + } + blockDevices = append(blockDevices, bd) + } } flavor, flavorReconcileStatus := dependency.FetchDependency( @@ -256,7 +286,7 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp serverCreateOpts := servers.CreateOpts{ Name: getResourceName(obj), - ImageRef: *image.Status.ID, + ImageRef: imageID, // Empty string if boot-from-volume FlavorRef: *flavor.Status.ID, Networks: portList, UserData: userData, @@ -264,6 +294,7 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp Metadata: metadata, AvailabilityZone: resource.AvailabilityZone, ConfigDrive: resource.ConfigDrive, + BlockDevice: blockDevices, // Boot volume for BFV } /* keypairs.CreateOptsExt was merged into servers.CreateOpts in gopher cloud V3 diff --git a/internal/controllers/server/controller.go b/internal/controllers/server/controller.go index 95ac9f595..c83381a9a 100644 --- a/internal/controllers/server/controller.go +++ b/internal/controllers/server/controller.go @@ -73,13 +73,31 @@ var ( "spec.resource.imageRef", func(server *orcv1alpha1.Server) []string { resource := server.Spec.Resource - if resource == nil { + if resource == nil || resource.ImageRef == nil { return nil } - return []string{string(resource.ImageRef)} + return []string{string(*resource.ImageRef)} + }, + finalizer, externalObjectFieldOwner, + ) + + // bootVolumeDependency handles the boot volume specified in bootVolume for boot-from-volume. + // This volume is attached at server creation time as the root disk. + // deletion guard is in place because the server cannot boot without its root volume. + // OverrideDependencyName is used to avoid conflict with volumeDependency which also + // creates a Volume deletion guard for Server. + bootVolumeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Volume]( + "spec.resource.bootVolume.volumeRef", + func(server *orcv1alpha1.Server) []string { + resource := server.Spec.Resource + if resource == nil || resource.BootVolume == nil { + return nil + } + return []string{string(resource.BootVolume.VolumeRef)} }, finalizer, externalObjectFieldOwner, + dependency.OverrideDependencyName("bootvolume"), ) portDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Port]( @@ -196,6 +214,10 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c if err != nil { return err } + bootVolumeWatchEventHandler, err := bootVolumeDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). @@ -215,6 +237,9 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c Watches(&orcv1alpha1.Volume{}, volumeWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Volume{})), ). + Watches(&orcv1alpha1.Volume{}, bootVolumeWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Volume{})), + ). Watches(&orcv1alpha1.KeyPair{}, keypairWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.KeyPair{})), ). @@ -234,6 +259,7 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c serverGroupDependency.AddToManager(ctx, mgr), userDataDependency.AddToManager(ctx, mgr), volumeDependency.AddToManager(ctx, mgr), + bootVolumeDependency.AddToManager(ctx, mgr), keypairDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency), diff --git a/internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml b/internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml new file mode 100644 index 000000000..4fa211ad6 --- /dev/null +++ b/internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml @@ -0,0 +1,61 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Server + name: server-boot-from-volume + ref: server + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Volume + name: server-boot-from-volume + ref: volume + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: server-boot-from-volume + ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: server-boot-from-volume + ref: network + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Subnet + name: server-boot-from-volume + ref: subnet +assertAll: + - celExpr: "server.status.resource.hostID != ''" + - celExpr: "server.status.resource.availabilityZone != ''" + # Verify the server booted from volume (imageID may be empty for BFV servers) + - celExpr: "port.status.resource.deviceID == server.status.id" + - celExpr: "port.status.resource.status == 'ACTIVE'" + - celExpr: "size(server.status.resource.interfaces) == 1" + - celExpr: "server.status.resource.interfaces[0].portID == port.status.id" + - celExpr: "server.status.resource.interfaces[0].netID == network.status.id" + - celExpr: "server.status.resource.interfaces[0].macAddr != ''" + - celExpr: "server.status.resource.interfaces[0].portState != ''" + - celExpr: "size(server.status.resource.interfaces[0].fixedIPs) >= 1" + - celExpr: "server.status.resource.interfaces[0].fixedIPs[0].ipAddress != ''" + - celExpr: "server.status.resource.interfaces[0].fixedIPs[0].subnetID == subnet.status.id" + # Verify volume is bootable + - celExpr: "volume.status.resource.bootable == true" + # Verify volume is attached to the server + - celExpr: "size(volume.status.resource.attachments) == 1" + - celExpr: "volume.status.resource.attachments[0].serverID == server.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Server +metadata: + name: server-boot-from-volume +status: + resource: + name: server-boot-from-volume + status: ACTIVE +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Volume +metadata: + name: server-boot-from-volume +status: + resource: + bootable: true + status: in-use diff --git a/internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml b/internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml new file mode 100644 index 000000000..e14a11c45 --- /dev/null +++ b/internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml @@ -0,0 +1,30 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: server-boot-from-volume +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: server-boot-from-volume + addresses: + - subnetRef: server-boot-from-volume +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Server +metadata: + name: server-boot-from-volume +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + # Note: No imageRef - booting from volume! + bootVolume: + volumeRef: server-boot-from-volume + flavorRef: server-boot-from-volume + ports: + - portRef: server-boot-from-volume diff --git a/internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml b/internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml new file mode 100644 index 000000000..a75dc4d64 --- /dev/null +++ b/internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml @@ -0,0 +1,63 @@ +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 + - script: | + export E2E_KUTTL_CURRENT_TEST=server-boot-from-volume + cat ../templates/create-flavor.tmpl | envsubst | kubectl -n ${NAMESPACE} apply -f - +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Image +metadata: + name: server-boot-from-volume +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + content: + diskFormat: qcow2 + download: + url: https://github.com/k-orc/openstack-resource-controller/raw/2ddc1857f5e22d2f0df6f5ee033353e4fd907121/internal/controllers/image/testdata/cirros-0.6.3-x86_64-disk.img + visibility: public +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: server-boot-from-volume +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: server-boot-from-volume +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: server-boot-from-volume +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: server-boot-from-volume + ipVersion: 4 + cidr: 192.168.201.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Volume +metadata: + name: server-boot-from-volume +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + size: 1 + imageRef: server-boot-from-volume diff --git a/internal/controllers/server/tests/server-boot-from-volume/README.md b/internal/controllers/server/tests/server-boot-from-volume/README.md new file mode 100644 index 000000000..0a26653ad --- /dev/null +++ b/internal/controllers/server/tests/server-boot-from-volume/README.md @@ -0,0 +1,14 @@ +# Boot from Volume Test + +This test creates a server that boots from a Cinder volume instead of an +image. This is the boot-from-volume (BFV) pattern where: + +1. An image is created +2. A bootable volume is created from that image +3. A server is created booting from the volume (no imageRef) + +The test verifies: +- Server reaches ACTIVE state +- Volume is marked as bootable +- Volume is attached to the server +- Port is attached to the server diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.go new file mode 100644 index 000000000..a8456ea36 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.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" +) + +// ServerBootVolumeSpecApplyConfiguration represents a declarative configuration of the ServerBootVolumeSpec type for use +// with apply. +type ServerBootVolumeSpecApplyConfiguration struct { + VolumeRef *apiv1alpha1.KubernetesNameRef `json:"volumeRef,omitempty"` + Tag *string `json:"tag,omitempty"` +} + +// ServerBootVolumeSpecApplyConfiguration constructs a declarative configuration of the ServerBootVolumeSpec type for use with +// apply. +func ServerBootVolumeSpec() *ServerBootVolumeSpecApplyConfiguration { + return &ServerBootVolumeSpecApplyConfiguration{} +} + +// WithVolumeRef sets the VolumeRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the VolumeRef field is set to the value of the last call. +func (b *ServerBootVolumeSpecApplyConfiguration) WithVolumeRef(value apiv1alpha1.KubernetesNameRef) *ServerBootVolumeSpecApplyConfiguration { + b.VolumeRef = &value + return b +} + +// WithTag sets the Tag field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Tag field is set to the value of the last call. +func (b *ServerBootVolumeSpecApplyConfiguration) WithTag(value string) *ServerBootVolumeSpecApplyConfiguration { + b.Tag = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go index c3308477a..9348ac5c4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go @@ -25,18 +25,19 @@ import ( // ServerResourceSpecApplyConfiguration represents a declarative configuration of the ServerResourceSpec type for use // with apply. type ServerResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - ImageRef *apiv1alpha1.KubernetesNameRef `json:"imageRef,omitempty"` - FlavorRef *apiv1alpha1.KubernetesNameRef `json:"flavorRef,omitempty"` - UserData *UserDataSpecApplyConfiguration `json:"userData,omitempty"` - Ports []ServerPortSpecApplyConfiguration `json:"ports,omitempty"` - Volumes []ServerVolumeSpecApplyConfiguration `json:"volumes,omitempty"` - ServerGroupRef *apiv1alpha1.KubernetesNameRef `json:"serverGroupRef,omitempty"` - AvailabilityZone *string `json:"availabilityZone,omitempty"` - KeypairRef *apiv1alpha1.KubernetesNameRef `json:"keypairRef,omitempty"` - Tags []apiv1alpha1.ServerTag `json:"tags,omitempty"` - Metadata []ServerMetadataApplyConfiguration `json:"metadata,omitempty"` - ConfigDrive *bool `json:"configDrive,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + ImageRef *apiv1alpha1.KubernetesNameRef `json:"imageRef,omitempty"` + FlavorRef *apiv1alpha1.KubernetesNameRef `json:"flavorRef,omitempty"` + BootVolume *ServerBootVolumeSpecApplyConfiguration `json:"bootVolume,omitempty"` + UserData *UserDataSpecApplyConfiguration `json:"userData,omitempty"` + Ports []ServerPortSpecApplyConfiguration `json:"ports,omitempty"` + Volumes []ServerVolumeSpecApplyConfiguration `json:"volumes,omitempty"` + ServerGroupRef *apiv1alpha1.KubernetesNameRef `json:"serverGroupRef,omitempty"` + AvailabilityZone *string `json:"availabilityZone,omitempty"` + 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 @@ -69,6 +70,14 @@ func (b *ServerResourceSpecApplyConfiguration) WithFlavorRef(value apiv1alpha1.K return b } +// WithBootVolume sets the BootVolume field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the BootVolume field is set to the value of the last call. +func (b *ServerResourceSpecApplyConfiguration) WithBootVolume(value *ServerBootVolumeSpecApplyConfiguration) *ServerResourceSpecApplyConfiguration { + b.BootVolume = value + return b +} + // WithUserData sets the UserData field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the UserData 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 34d65a029..419e61894 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2600,6 +2600,15 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerStatus default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerBootVolumeSpec + map: + fields: + - name: tag + type: + scalar: string + - name: volumeRef + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerFilter map: fields: @@ -2816,6 +2825,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: availabilityZone type: scalar: string + - name: bootVolume + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerBootVolumeSpec - name: configDrive type: scalar: boolean diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 54b063b6d..618c6c9b7 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -316,6 +316,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.SecurityGroupStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Server"): return &apiv1alpha1.ServerApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ServerBootVolumeSpec"): + return &apiv1alpha1.ServerBootVolumeSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerFilter"): return &apiv1alpha1.ServerFilterApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerGroup"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index caf1705ca..12e26692e 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2163,6 +2163,7 @@ _Appears in:_ - [RouterResourceSpec](#routerresourcespec) - [SecurityGroupFilter](#securitygroupfilter) - [SecurityGroupResourceSpec](#securitygroupresourcespec) +- [ServerBootVolumeSpec](#serverbootvolumespec) - [ServerPortSpec](#serverportspec) - [ServerResourceSpec](#serverresourcespec) - [ServerVolumeSpec](#servervolumespec) @@ -3599,6 +3600,24 @@ Server is the Schema for an ORC resource. | `status` _[ServerStatus](#serverstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| +#### ServerBootVolumeSpec + + + +ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation. +When specified, the server boots from this volume instead of an image. + + + +_Appears in:_ +- [ServerResourceSpec](#serverresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `volumeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeRef is a reference to a Volume object. The volume must be
bootable (created from an image) and available before server creation. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `tag` _string_ | tag is the device tag applied to the volume. | | MaxLength: 255
Optional: \{\}
| + + #### ServerFilter @@ -3928,8 +3947,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: `^[^,]+$`
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: \{\}
| +| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef references the image to use for the server instance.
This field is required unless bootVolume is specified for boot-from-volume. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| | `flavorRef` _[KubernetesNameRef](#kubernetesnameref)_ | flavorRef references the flavor to use for the server instance. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `bootVolume` _[ServerBootVolumeSpec](#serverbootvolumespec)_ | bootVolume specifies a volume to boot from instead of an image.
When specified, imageRef must be omitted. The volume must be
bootable (created from an image using imageRef in the Volume spec). | | Optional: \{\}
| | `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: \{\}
| From 80aa64d882a98a9d02d2d0b7a864de8262f81a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 5 Feb 2026 12:49:38 +0100 Subject: [PATCH 166/237] Simplify dependency ready filters to use IsAvailable directly Since IsAvailable implies Status.ID is set, the redundant check for Status.ID != nil can be removed. Made IsAvailable generic to allow passing it directly as a filter function, eliminating boilerplate lambdas across all controllers. This makes the code more concise and ensures consistent availability checking for all dependencies. --- api/v1alpha1/conditions.go | 2 +- .../data/controller/actuator.go.template | 24 ++++++------- internal/controllers/addressscope/actuator.go | 8 ++--- .../applicationcredential/actuator.go | 24 +++++-------- internal/controllers/endpoint/actuator.go | 12 +++---- internal/controllers/floatingip/actuator.go | 34 ++++++------------- internal/controllers/group/actuator.go | 10 ++---- internal/controllers/network/actuator.go | 10 ++---- internal/controllers/port/actuator.go | 32 +++++------------ internal/controllers/project/actuator.go | 10 ++---- internal/controllers/role/actuator.go | 10 ++---- internal/controllers/router/actuator.go | 14 +++----- .../controllers/securitygroup/actuator.go | 14 +++----- internal/controllers/server/actuator.go | 24 +++++-------- internal/controllers/subnet/actuator.go | 24 ++++--------- internal/controllers/trunk/actuator.go | 24 +++++-------- internal/controllers/user/actuator.go | 12 +++---- internal/controllers/volume/actuator.go | 10 ++---- 18 files changed, 95 insertions(+), 203 deletions(-) diff --git a/api/v1alpha1/conditions.go b/api/v1alpha1/conditions.go index 93dfe7712..4acd7f1f0 100644 --- a/api/v1alpha1/conditions.go +++ b/api/v1alpha1/conditions.go @@ -113,7 +113,7 @@ func GetTerminalError(obj ObjectWithConditions) error { return nil } -func IsAvailable(obj ObjectWithConditions) bool { +func IsAvailable[T ObjectWithConditions](obj T) bool { conditions := obj.GetConditions() available := meta.FindStatusCondition(conditions, ConditionAvailable) diff --git a/cmd/scaffold-controller/data/controller/actuator.go.template b/cmd/scaffold-controller/data/controller/actuator.go.template index ac6c2af62..50d4d65e1 100644 --- a/cmd/scaffold-controller/data/controller/actuator.go.template +++ b/cmd/scaffold-controller/data/controller/actuator.go.template @@ -106,10 +106,10 @@ func (actuator {{ .PackageName }}Actuator) ListOSResourcesForImport(ctx context. var reconcileStatus progress.ReconcileStatus {{- range .ImportDependencies }} {{ $depNameCamelCase := . | camelCase }} - {{ $depNameCamelCase }}, rs := dependency.FetchDependency( + {{ $depNameCamelCase }}, rs := dependency.FetchDependency[*orcv1alpha1.{{ . }}]( ctx, actuator.k8sClient, obj.Namespace, filter.{{ . }}Ref, "{{ . }}", - func(dep *orcv1alpha1.{{ . }}) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) {{- end }} @@ -145,15 +145,13 @@ func (actuator {{ .PackageName }}Actuator) CreateResource(ctx context.Context, o {{- range .RequiredCreateDependencies }} {{ $depNameCamelCase := . | camelCase }} var {{ $depNameCamelCase }}ID string - {{ $depNameCamelCase }}, {{ $depNameCamelCase }}DepRS := {{ $depNameCamelCase }}Dependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.{{ . }}) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus({{ $depNameCamelCase }}DepRS) - if {{ $depNameCamelCase }} != nil { - {{ $depNameCamelCase }}ID = ptr.Deref({{ $depNameCamelCase }}.Status.ID, "") - } + {{ $depNameCamelCase }}, {{ $depNameCamelCase }}DepRS := {{ $depNameCamelCase }}Dependency.GetDependency( + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus({{ $depNameCamelCase }}DepRS) + if {{ $depNameCamelCase }} != nil { + {{ $depNameCamelCase }}ID = ptr.Deref({{ $depNameCamelCase }}.Status.ID, "") + } {{- end }} {{- range .OptionalCreateDependencies }} @@ -161,9 +159,7 @@ func (actuator {{ .PackageName }}Actuator) CreateResource(ctx context.Context, o var {{ $depNameCamelCase }}ID string if resource.{{ . }}Ref != nil { {{ $depNameCamelCase }}, {{ $depNameCamelCase }}DepRS := {{ $depNameCamelCase }}Dependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.{{ . }}) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus({{ $depNameCamelCase }}DepRS) if {{ $depNameCamelCase }} != nil { diff --git a/internal/controllers/addressscope/actuator.go b/internal/controllers/addressscope/actuator.go index dd062fe9f..a737393df 100644 --- a/internal/controllers/addressscope/actuator.go +++ b/internal/controllers/addressscope/actuator.go @@ -81,10 +81,10 @@ func (actuator addressscopeActuator) ListOSResourcesForAdoption(ctx context.Cont func (actuator addressscopeActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - project, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -115,9 +115,7 @@ func (actuator addressscopeActuator) CreateResource(ctx context.Context, obj orc 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { diff --git a/internal/controllers/applicationcredential/actuator.go b/internal/controllers/applicationcredential/actuator.go index 6e1c30e0c..71989a676 100644 --- a/internal/controllers/applicationcredential/actuator.go +++ b/internal/controllers/applicationcredential/actuator.go @@ -70,10 +70,10 @@ func (actuator applicationcredentialActuator) ListOSResourcesForAdoption(ctx con return nil, false } - user, _ := dependency.FetchDependency( + user, _ := dependency.FetchDependency[*orcv1alpha1.User]( ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.UserRef, "User", - func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) if user.Status.ID == nil { @@ -99,10 +99,10 @@ func (actuator applicationcredentialActuator) ListOSResourcesForAdoption(ctx con func (actuator applicationcredentialActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - user, rs := dependency.FetchDependency( + user, rs := dependency.FetchDependency[*orcv1alpha1.User]( ctx, actuator.k8sClient, obj.Namespace, &filter.UserRef, "User", - func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -143,21 +143,15 @@ func (actuator applicationcredentialActuator) CreateResource(ctx context.Context var reconcileStatus progress.ReconcileStatus user, userDepRS := userDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) rolesMap, roleDepRs := roleDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) serviceMap, serviceDepRS := serviceDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) secret, secretReconcileStatus := dependency.FetchDependency( @@ -250,9 +244,7 @@ func (actuator applicationcredentialActuator) DeleteResource(ctx context.Context var reconcileStatus progress.ReconcileStatus user, userDepRS := userDependency.GetDependency( - ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.User) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, orcObject, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS) diff --git a/internal/controllers/endpoint/actuator.go b/internal/controllers/endpoint/actuator.go index 71988f403..7c74ee14e 100644 --- a/internal/controllers/endpoint/actuator.go +++ b/internal/controllers/endpoint/actuator.go @@ -73,9 +73,7 @@ func (actuator endpointActuator) ListOSResourcesForAdoption(ctx context.Context, } service, _ := serviceDependency.GetDependency( - ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.Service) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, orcObject, orcv1alpha1.IsAvailable, ) if service == nil { @@ -99,10 +97,10 @@ func (actuator endpointActuator) ListOSResourcesForAdoption(ctx context.Context, func (actuator endpointActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - service, rs := dependency.FetchDependency( + service, rs := dependency.FetchDependency[*orcv1alpha1.Service]( ctx, actuator.k8sClient, obj.Namespace, filter.ServiceRef, "Service", - func(dep *orcv1alpha1.Service) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -142,9 +140,7 @@ func (actuator endpointActuator) CreateResource(ctx context.Context, obj orcObje var serviceID string service, serviceDepRS := serviceDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Service) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(serviceDepRS) diff --git a/internal/controllers/floatingip/actuator.go b/internal/controllers/floatingip/actuator.go index 8a99c564e..3290de74f 100644 --- a/internal/controllers/floatingip/actuator.go +++ b/internal/controllers/floatingip/actuator.go @@ -88,27 +88,21 @@ 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, rs := dependency.FetchDependency( + network, rs := dependency.FetchDependency[*orcv1alpha1.Network]( ctx, actuator.k8sClient, obj.Namespace, filter.FloatingNetworkRef, "Network", - func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - port, rs := dependency.FetchDependency( + port, rs := dependency.FetchDependency[*orcv1alpha1.Port]( ctx, actuator.k8sClient, obj.Namespace, filter.PortRef, "Port", - func(dep *orcv1alpha1.Port) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -145,9 +139,7 @@ func (actuator floatingipCreateActuator) CreateResource(ctx context.Context, obj if resource.FloatingNetworkRef != nil { // Fetch dependencies and ensure they have our finalizer network, networkDepRS := networkDep.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(networkDepRS) if network != nil { @@ -160,9 +152,7 @@ func (actuator floatingipCreateActuator) CreateResource(ctx context.Context, obj if resource.FloatingSubnetRef != nil { // Fetch dependencies and ensure they have our finalizer subnet, subnetDepRS := subnetDep.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Subnet) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(subnetDepRS) if subnet != nil { @@ -175,9 +165,7 @@ func (actuator floatingipCreateActuator) CreateResource(ctx context.Context, obj if resource.PortRef != nil { // Fetch dependencies and ensure they have our finalizer port, portDepRS := portDep.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS) if port != nil { @@ -188,9 +176,7 @@ func (actuator floatingipCreateActuator) CreateResource(ctx context.Context, obj 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { diff --git a/internal/controllers/group/actuator.go b/internal/controllers/group/actuator.go index afdaad252..856e789b2 100644 --- a/internal/controllers/group/actuator.go +++ b/internal/controllers/group/actuator.go @@ -82,11 +82,9 @@ func (actuator groupActuator) ListOSResourcesForImport(ctx context.Context, obj var reconcileStatus progress.ReconcileStatus - domain, rs := dependency.FetchDependency( + domain, rs := dependency.FetchDependency[*orcv1alpha1.Domain]( ctx, actuator.k8sClient, obj.Namespace, filter.DomainRef, "Domain", - func(dep *orcv1alpha1.Domain) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -115,9 +113,7 @@ func (actuator groupActuator) CreateResource(ctx context.Context, obj orcObjectP 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) if domain != nil { diff --git a/internal/controllers/network/actuator.go b/internal/controllers/network/actuator.go index ffb7a147b..68de554ff 100644 --- a/internal/controllers/network/actuator.go +++ b/internal/controllers/network/actuator.go @@ -83,11 +83,9 @@ 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, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -118,9 +116,7 @@ func (actuator networkActuator) CreateResource(ctx context.Context, obj orcObjec 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 363ae931d..9e8bd2a67 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -128,19 +128,15 @@ 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, rs := dependency.FetchDependency( + network, rs := dependency.FetchDependency[*orcv1alpha1.Network]( ctx, actuator.k8sClient, obj.Namespace, &filter.NetworkRef, "Network", - func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -173,19 +169,13 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha // Fetch all dependencies and ensure they have our finalizer network, networkDepRS := networkDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) subnetMap, subnetDepRS := subnetDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Subnet) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) secGroupMap, secGroupDepRS := securityGroupDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.SecurityGroup) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus := progress.NewReconcileStatus(). WithReconcileStatus(networkDepRS). @@ -195,9 +185,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { @@ -369,9 +357,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT } secGroupMap, secGroupDepRS := securityGroupDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.SecurityGroup) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus := progress.NewReconcileStatus(). diff --git a/internal/controllers/project/actuator.go b/internal/controllers/project/actuator.go index ca156cee0..412700730 100644 --- a/internal/controllers/project/actuator.go +++ b/internal/controllers/project/actuator.go @@ -90,11 +90,9 @@ func (actuator projectActuator) ListOSResourcesForAdoption(ctx context.Context, func (actuator projectActuator) ListOSResourcesForImport(ctx context.Context, orcObject orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - domain, rs := dependency.FetchDependency( + domain, rs := dependency.FetchDependency[*orcv1alpha1.Domain]( ctx, actuator.k8sClient, orcObject.Namespace, filter.DomainRef, "Domain", - func(dep *orcv1alpha1.Domain) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -127,9 +125,7 @@ func (actuator projectActuator) CreateResource(ctx context.Context, obj orcObjec 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) if domain != nil { diff --git a/internal/controllers/role/actuator.go b/internal/controllers/role/actuator.go index 1d5be1885..3e4989ae0 100644 --- a/internal/controllers/role/actuator.go +++ b/internal/controllers/role/actuator.go @@ -93,11 +93,9 @@ 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, rs := dependency.FetchDependency( + domain, rs := dependency.FetchDependency[*orcv1alpha1.Domain]( ctx, actuator.k8sClient, obj.Namespace, filter.DomainRef, "Domain", - func(dep *orcv1alpha1.Domain) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -126,9 +124,7 @@ func (actuator roleActuator) CreateResource(ctx context.Context, obj orcObjectPT 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) if domain != nil { diff --git a/internal/controllers/router/actuator.go b/internal/controllers/router/actuator.go index 59768482e..e6ec8fa64 100644 --- a/internal/controllers/router/actuator.go +++ b/internal/controllers/router/actuator.go @@ -83,11 +83,9 @@ 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, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -123,9 +121,7 @@ func (actuator routerCreateActuator) CreateResource(ctx context.Context, obj *or var externalGW *orcv1alpha1.Network // Fetch dependencies and ensure they have our finalizer externalGW, reconcileStatus = externalGWDep.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) if externalGW != nil { gatewayInfo.NetworkID = ptr.Deref(externalGW.Status.ID, "") @@ -135,9 +131,7 @@ func (actuator routerCreateActuator) CreateResource(ctx context.Context, obj *or 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index 889722e4b..58ff4194a 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -88,11 +88,9 @@ 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, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -123,9 +121,7 @@ func (actuator securityGroupActuator) CreateResource(ctx context.Context, obj *o 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus @@ -280,9 +276,7 @@ func (actuator securityGroupActuator) updateRules(ctx context.Context, orcObject var projectID string if resource.ProjectRef != nil { project, reconcileStatus := projectDependency.GetDependency( - ctx, actuator.k8sClient, orcObject, func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, orcObject, orcv1alpha1.IsAvailable, ) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return reconcileStatus diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index a044503f6..12514d240 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -163,27 +163,23 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp var image *orcv1alpha1.Image { dep, imageReconcileStatus := imageDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(image *orcv1alpha1.Image) bool { - return orcv1alpha1.IsAvailable(image) && image.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(imageReconcileStatus) image = dep } - flavor, flavorReconcileStatus := dependency.FetchDependency( + flavor, flavorReconcileStatus := dependency.FetchDependency[*orcv1alpha1.Flavor]( ctx, actuator.k8sClient, obj.Namespace, &resource.FlavorRef, "Flavor", - func(f *orcv1alpha1.Flavor) bool { return orcv1alpha1.IsAvailable(f) && f.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(flavorReconcileStatus) portList := make([]servers.Network, len(resource.Ports)) { portsMap, portsReconcileStatus := portDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(port *orcv1alpha1.Port) bool { - return orcv1alpha1.IsAvailable(port) && port.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(portsReconcileStatus) if needsReschedule, _ := portsReconcileStatus.NeedsReschedule(); !needsReschedule { @@ -206,10 +202,10 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp } } - serverGroup, serverGroupReconcileStatus := dependency.FetchDependency( + serverGroup, serverGroupReconcileStatus := dependency.FetchDependency[*orcv1alpha1.ServerGroup]( ctx, actuator.k8sClient, obj.Namespace, resource.ServerGroupRef, "ServerGroup", - func(sg *orcv1alpha1.ServerGroup) bool { return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(serverGroupReconcileStatus) @@ -444,9 +440,7 @@ func (actuator serverActuator) reconcilePortAttachments(ctx context.Context, obj } portDepsMap, reconcileStatus := portDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(port *orcv1alpha1.Port) bool { - return orcv1alpha1.IsAvailable(port) && port.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { @@ -524,9 +518,7 @@ func (actuator serverActuator) reconcileVolumeAttachments(ctx context.Context, o } volumeDepsMap, reconcileStatus := volumeDependency.GetDependencies( - ctx, actuator.k8sClient, obj, func(volume *orcv1alpha1.Volume) bool { - return orcv1alpha1.IsAvailable(volume) && volume.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { diff --git a/internal/controllers/subnet/actuator.go b/internal/controllers/subnet/actuator.go index f3e0e8dd4..a8c2094d8 100644 --- a/internal/controllers/subnet/actuator.go +++ b/internal/controllers/subnet/actuator.go @@ -87,19 +87,15 @@ 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, rs := dependency.FetchDependency( + network, rs := dependency.FetchDependency[*orcv1alpha1.Network]( ctx, actuator.k8sClient, obj.Namespace, &filter.NetworkRef, "Network", - func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -137,16 +133,12 @@ func (actuator subnetActuator) CreateResource(ctx context.Context, obj orcObject } network, reconcileStatus := networkDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) if resource.RouterRef != nil { _, routerDepRS := routerDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Router) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(routerDepRS) } @@ -154,9 +146,7 @@ func (actuator subnetActuator) CreateResource(ctx context.Context, obj orcObject 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { diff --git a/internal/controllers/trunk/actuator.go b/internal/controllers/trunk/actuator.go index 86cd107fb..520ff2b9c 100644 --- a/internal/controllers/trunk/actuator.go +++ b/internal/controllers/trunk/actuator.go @@ -84,17 +84,17 @@ func (actuator trunkActuator) ListOSResourcesForAdoption(ctx context.Context, or func (actuator trunkActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - port, rs := dependency.FetchDependency( + port, rs := dependency.FetchDependency[*orcv1alpha1.Port]( ctx, actuator.k8sClient, obj.Namespace, filter.PortRef, "Port", - func(dep *orcv1alpha1.Port) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) - project, rs := dependency.FetchDependency( + project, rs := dependency.FetchDependency[*orcv1alpha1.Project]( ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -129,9 +129,7 @@ func (actuator trunkActuator) CreateResource(ctx context.Context, obj orcObjectP var portID string port, portDepRS := portDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Port) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(portDepRS) if port != nil { @@ -141,9 +139,7 @@ func (actuator trunkActuator) CreateResource(ctx context.Context, obj orcObjectP 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { @@ -155,9 +151,7 @@ func (actuator trunkActuator) CreateResource(ctx context.Context, obj orcObjectP 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(subportPortDepRS) if needsReschedule, _ := subportPortDepRS.NeedsReschedule(); !needsReschedule { @@ -293,9 +287,7 @@ func (actuator trunkActuator) reconcileSubports(ctx context.Context, obj orcObje 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(subportPortDepRS) if needsReschedule, _ := subportPortDepRS.NeedsReschedule(); needsReschedule { diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index d82014cab..3658c0d20 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -84,10 +84,10 @@ func (actuator userActuator) ListOSResourcesForAdoption(ctx context.Context, orc func (actuator userActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { var reconcileStatus progress.ReconcileStatus - domain, rs := dependency.FetchDependency( + domain, rs := dependency.FetchDependency[*orcv1alpha1.Domain]( ctx, actuator.k8sClient, obj.Namespace, filter.DomainRef, "Domain", - func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -116,9 +116,7 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) if domain != nil { @@ -129,9 +127,7 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT 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 - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) if project != nil { diff --git a/internal/controllers/volume/actuator.go b/internal/controllers/volume/actuator.go index 2fbde2b44..17c21352b 100644 --- a/internal/controllers/volume/actuator.go +++ b/internal/controllers/volume/actuator.go @@ -156,9 +156,7 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject var volumetypeID string if resource.VolumeTypeRef != nil { volumetype, volumetypeDepRS := volumetypeDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.VolumeType) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(volumetypeDepRS) if volumetype != nil { @@ -167,12 +165,10 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject } // Resolve image dependency for bootable volumes - image, imageDepRS := dependency.FetchDependency( + image, imageDepRS := dependency.FetchDependency[*orcv1alpha1.Image]( ctx, actuator.k8sClient, obj.Namespace, resource.ImageRef, "Image", - func(dep *orcv1alpha1.Image) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(imageDepRS) imageID := ptr.Deref(image.Status.ID, "") From 432e7e7a3b801ffba50e4d686823e9953547aefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 17:42:28 +0200 Subject: [PATCH 167/237] Fix api validation tests after rebase --- test/apivalidations/server_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/apivalidations/server_test.go b/test/apivalidations/server_test.go index 058fc8b2b..360ee9865 100644 --- a/test/apivalidations/server_test.go +++ b/test/apivalidations/server_test.go @@ -112,11 +112,11 @@ var _ = Describe("ORC Server API validations", func() { WithPorts(applyconfigv1alpha1.ServerPortSpec().WithPortRef("my-port"))) Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("spec.resource.flavorRef"))) - // Missing imageRef + // Missing imageRef or bootVolume 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"))) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("either imageRef or bootVolume must be specified"))) // Missing ports patch.Spec.WithResource(applyconfigv1alpha1.ServerResourceSpec(). From 14ed9dffcd78f24bb577dbe5610930f81de8aae0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:45:31 +0000 Subject: [PATCH 168/237] :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.11.1 to 2.12.0 - [Release notes](https://github.com/gophercloud/gophercloud/releases) - [Changelog](https://github.com/gophercloud/gophercloud/blob/v2.12.0/CHANGELOG.md) - [Commits](https://github.com/gophercloud/gophercloud/compare/v2.11.1...v2.12.0) --- updated-dependencies: - dependency-name: github.com/gophercloud/gophercloud/v2 dependency-version: 2.12.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 | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a2287aeda..9ea974d27 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,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.11.1 + github.com/gophercloud/gophercloud/v2 v2.12.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 ea0cf8879..11a89909d 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.1 h1:jCs4vLH8sJgRqrPzqVfWgl7uI6JnIIlsgeIRM0uHjxY= -github.com/gophercloud/gophercloud/v2 v2.11.1/go.mod h1:Rm0YvKQ4QYX2rY9XaDKnjRzSGwlG5ge4h6ABYnmkKQM= +github.com/gophercloud/gophercloud/v2 v2.12.0 h1:Gxmc/Bog1UDKkxTcQW7MSPTDviJXpLeEgVeN5KrxoCo= +github.com/gophercloud/gophercloud/v2 v2.12.0/go.mod h1:H7TTOxbLy8RIaHSNhI2GCrWIzw4Xpw8Xn2mBhCUT5kA= 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 fec833d8b974cf2f4df046c8153cfe9b6ef044e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:46:43 +0000 Subject: [PATCH 169/237] :seedling:(deps): Bump the all-github-actions group with 2 updates Bumps the all-github-actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action). Updates `actions/cache` from 5.0.4 to 5.0.5 - [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/668228422ae6a00e4ad889ee87cd7109ec5666a7...27d5ce7f107fe9357f9df03efb73ab90386fccae) Updates `zizmorcore/zizmor-action` from 0.5.2 to 0.5.3 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/71321a20a9ded102f6e9ce5718a2fcec2c4f70d8...b1d7e1fb5de872772f31590499237e7cce841e8e) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.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 +- .github/workflows/zizmor.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index 7126e9e82..a9b043e38 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -29,7 +29,7 @@ jobs: 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 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # tag=v5.0.5 name: Restore go cache with: path: | diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index dc96f7d5e..f10734b40 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -25,4 +25,4 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # tag=v0.5.2 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # tag=v0.5.3 From a2b2b1938f611434b281429f41657707fe7f9487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 23 Apr 2026 12:02:41 +0200 Subject: [PATCH 170/237] Fix port status not updating to ACTIVE after server interface attachment When the server controller attaches a port via Nova os-interface, the port's Neutron status transitions asynchronously from DOWN to ACTIVE as OVN binds the port. The port controller may reconcile after device_id is set but before the status becomes ACTIVE, writing DOWN to the port's status and setting Progressing: False. The existing serverToPortMapFunc watch handler only triggers port re-reconciliation when DeviceID disagrees with the server's interface list. When the port controller already picked up the device_id but not the ACTIVE status, DeviceID matches and no re-reconciliation is triggered, leaving the port status permanently stale at DOWN. Add a check for ports that are listed in the server's interfaces and have the correct DeviceID but still show status DOWN, triggering a re-reconciliation to pick up the current Neutron status. --- internal/controllers/port/controller.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/controllers/port/controller.go b/internal/controllers/port/controller.go index ae0d73b37..2d9f881fc 100644 --- a/internal/controllers/port/controller.go +++ b/internal/controllers/port/controller.go @@ -208,6 +208,12 @@ func serverToPortMapFunc(ctx context.Context, k8sClient client.Client) handler.M log.V(logging.Verbose).Info("port needs reconciliation: listed in server status but deviceID not set", "port", client.ObjectKeyFromObject(port), "server", client.ObjectKeyFromObject(server)) + } else if portStatus.Status == PortStatusDown { + shouldReconcile = true + reason = "Port attached to server but status is still DOWN" + log.V(logging.Verbose).Info("port needs reconciliation: attached to server but status is DOWN", + "port", client.ObjectKeyFromObject(port), + "server", client.ObjectKeyFromObject(server)) } } From 31e1e0f350fa7509bbdd7a31b0c51ddbe1a61ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 23 Apr 2026 12:08:07 +0200 Subject: [PATCH 171/237] Fix volume status not updating to in-use after server attachment Apply the same fix as the port controller: when serverToVolumeMapFunc detects a volume that is listed in the server's interfaces and already has the correct attachment info, but the volume's Cinder status is not in-use, trigger a re-reconciliation to pick up the current status from Cinder. --- internal/controllers/volume/controller.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/controllers/volume/controller.go b/internal/controllers/volume/controller.go index fb64c2c75..1438d422e 100644 --- a/internal/controllers/volume/controller.go +++ b/internal/controllers/volume/controller.go @@ -162,6 +162,12 @@ func serverToVolumeMapFunc(ctx context.Context, k8sClient client.Client) handler log.V(logging.Verbose).Info("volume needs reconciliation: listed in server status but no attachment info", "volume", client.ObjectKeyFromObject(volume), "server", client.ObjectKeyFromObject(server)) + } else if volumeStatus.Status != VolumeStatusInUse { + shouldReconcile = true + reason = "Volume attached to server but status is not in-use" + log.V(logging.Verbose).Info("volume needs reconciliation: attached to server but status is not in-use", + "volume", client.ObjectKeyFromObject(volume), + "server", client.ObjectKeyFromObject(server)) } } From c4af4c217c7f2c0745f7e7c3c67d93e48da5389d Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Thu, 15 Jan 2026 14:06:50 +0200 Subject: [PATCH 172/237] Add SchedulerHints to server controller - Add SchedulerHints to server controller - enable required nova filters for testing hints NOTE! this change MOVED the ServerGroupRef inside the ServerSchedulerHints --- .github/workflows/e2e.yaml | 4 + api/v1alpha1/server_types.go | 64 ++++++++- api/v1alpha1/zz_generated.deepcopy.go | 57 +++++++- cmd/models-schema/zz_generated.openapi.go | 133 ++++++++++++++++-- .../bases/openstack.k-orc.cloud_servers.yaml | 79 +++++++++-- config/samples/openstack_v1alpha1_server.yaml | 3 +- internal/controllers/server/actuator.go | 107 ++++++++++++-- internal/controllers/server/controller.go | 56 +++++++- .../tests/server-create-full/00-assert.yaml | 10 ++ .../00-create-resource.yaml | 38 ++++- .../00-create-everything-but-flavor.yaml | 3 +- .../01-create-everything-but-image.yaml | 3 +- .../02-create-everything-but-port.yaml | 3 +- ...03-create-everything-but-server-group.yaml | 3 +- ...create-everything-but-userdata-secret.yaml | 3 +- .../05-create-everything-but-keypair.yaml | 3 +- .../server-update/00-minimal-resource.yaml | 3 +- .../api/v1alpha1/serverresourcespec.go | 18 +-- .../api/v1alpha1/serverschedulerhints.go | 118 ++++++++++++++++ .../applyconfiguration/internal/internal.go | 42 +++++- pkg/clients/applyconfiguration/utils.go | 2 + website/docs/crd-reference.md | 27 +++- 22 files changed, 717 insertions(+), 62 deletions(-) create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 223568713..9200a5811 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -39,6 +39,10 @@ jobs: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} enabled_services: "openstack-cli-server,neutron-trunk" + conf_overrides: | + [[post-config|/etc/nova/nova.conf]] + [filter_scheduler] + enabled_filters = ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter,SameHostFilter,DifferentHostFilter,SimpleCIDRAffinityFilter,JsonFilter - name: Deploy a Kind Cluster uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # tag=v1.14.0 diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go index 381cabb25..af3aa8fee 100644 --- a/api/v1alpha1/server_types.go +++ b/api/v1alpha1/server_types.go @@ -181,12 +181,6 @@ type ServerResourceSpec struct { // +optional Volumes []ServerVolumeSpec `json:"volumes,omitempty"` - // serverGroupRef is a reference to a ServerGroup object. The server - // will be created in the server group. - // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serverGroupRef is immutable" - ServerGroupRef *KubernetesNameRef `json:"serverGroupRef,omitempty"` - // availabilityZone is the availability zone in which to create the server. // +kubebuilder:validation:MaxLength=255 // +optional @@ -217,6 +211,11 @@ type ServerResourceSpec struct { // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="configDrive is immutable" ConfigDrive *bool `json:"configDrive,omitempty"` + + // schedulerHints provides hints to the Nova scheduler for server placement. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="schedulerHints is immutable" + SchedulerHints *ServerSchedulerHints `json:"schedulerHints,omitempty"` } // ServerMetadata represents a key-value pair for server metadata. @@ -234,8 +233,59 @@ type ServerMetadata struct { Value string `json:"value,omitempty"` } -// +kubebuilder:validation:MinProperties:=1 +// ServerSchedulerHints provides hints to the Nova scheduler for server placement. +type ServerSchedulerHints struct { + // serverGroupRef is a reference to a ServerGroup object. The server will be + // scheduled on a host in the specified server group. + // +optional + ServerGroupRef *KubernetesNameRef `json:"serverGroupRef,omitempty"` + + // differentHostServerRefs is a list of references to Server objects. + // The server will be scheduled on a different host than all specified servers. + // +listType=set + // +kubebuilder:validation:MaxItems:=64 + // +optional + DifferentHostServerRefs []KubernetesNameRef `json:"differentHostServerRefs,omitempty"` + + // sameHostServerRefs is a list of references to Server objects. + // The server will be scheduled on the same host as all specified servers. + // +listType=set + // +kubebuilder:validation:MaxItems:=64 + // +optional + SameHostServerRefs []KubernetesNameRef `json:"sameHostServerRefs,omitempty"` + + // query is a conditional statement that results in compute nodes + // able to host the server. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + Query string `json:"query,omitempty"` + + // targetCell is a cell name where the server will be placed. + // +kubebuilder:validation:MaxLength:=255 + // +optional + TargetCell string `json:"targetCell,omitempty"` + + // differentCell is a list of cell names where the server should not + // be placed. + // +listType=set + // +kubebuilder:validation:MaxItems:=64 + // +kubebuilder:validation:items:MaxLength=1024 + // +optional + DifferentCell []string `json:"differentCell,omitempty"` + + // buildNearHostIP specifies a subnet of compute nodes to host the server. + // The host IP should be provided in an CIDR format like 10.10.10.10/24. + // +optional + BuildNearHostIP *CIDR `json:"buildNearHostIP,omitempty"` + + // additionalProperties is a map of arbitrary key/value pairs that are + // not validated by Nova. + // +optional + AdditionalProperties map[string]string `json:"additionalProperties,omitempty"` +} + // +kubebuilder:validation:MaxProperties:=1 +// +kubebuilder:validation:MinProperties:=1 type UserDataSpec struct { // secretRef is a reference to a Secret containing the user data for this server. // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6f5bf196d..05d5be17c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5116,11 +5116,6 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.ServerGroupRef != nil { - in, out := &in.ServerGroupRef, &out.ServerGroupRef - *out = new(KubernetesNameRef) - **out = **in - } if in.KeypairRef != nil { in, out := &in.KeypairRef, &out.KeypairRef *out = new(KubernetesNameRef) @@ -5141,6 +5136,11 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) { *out = new(bool) **out = **in } + if in.SchedulerHints != nil { + in, out := &in.SchedulerHints, &out.SchedulerHints + *out = new(ServerSchedulerHints) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerResourceSpec. @@ -5195,6 +5195,53 @@ func (in *ServerResourceStatus) DeepCopy() *ServerResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerSchedulerHints) DeepCopyInto(out *ServerSchedulerHints) { + *out = *in + if in.ServerGroupRef != nil { + in, out := &in.ServerGroupRef, &out.ServerGroupRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.DifferentHostServerRefs != nil { + in, out := &in.DifferentHostServerRefs, &out.DifferentHostServerRefs + *out = make([]KubernetesNameRef, len(*in)) + copy(*out, *in) + } + if in.SameHostServerRefs != nil { + in, out := &in.SameHostServerRefs, &out.SameHostServerRefs + *out = make([]KubernetesNameRef, len(*in)) + copy(*out, *in) + } + if in.DifferentCell != nil { + in, out := &in.DifferentCell, &out.DifferentCell + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.BuildNearHostIP != nil { + in, out := &in.BuildNearHostIP, &out.BuildNearHostIP + *out = new(CIDR) + **out = **in + } + if in.AdditionalProperties != nil { + in, out := &in.AdditionalProperties, &out.AdditionalProperties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSchedulerHints. +func (in *ServerSchedulerHints) DeepCopy() *ServerSchedulerHints { + if in == nil { + return nil + } + out := new(ServerSchedulerHints) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerSpec) DeepCopyInto(out *ServerSpec) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 557911992..4a6dfd9f8 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -209,6 +209,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "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), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSchedulerHints": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSchedulerHints(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeSpec(ref), @@ -9670,13 +9671,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref }, }, }, - "serverGroupRef": { - SchemaProps: spec.SchemaProps{ - Description: "serverGroupRef is a reference to a ServerGroup object. The server will be created in the server group.", - Type: []string{"string"}, - Format: "", - }, - }, "availabilityZone": { SchemaProps: spec.SchemaProps{ Description: "availabilityZone is the availability zone in which to create the server.", @@ -9737,12 +9731,18 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref Format: "", }, }, + "schedulerHints": { + SchemaProps: spec.SchemaProps{ + Description: "schedulerHints provides hints to the Nova scheduler for server placement.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSchedulerHints"), + }, + }, }, Required: []string{"flavorRef", "ports"}, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec", "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"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec", "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.ServerSchedulerHints", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec"}, } } @@ -9900,6 +9900,123 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(r } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ServerSchedulerHints(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServerSchedulerHints provides hints to the Nova scheduler for server placement.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "serverGroupRef": { + SchemaProps: spec.SchemaProps{ + Description: "serverGroupRef is a reference to a ServerGroup object. The server will be scheduled on a host in the specified server group.", + Type: []string{"string"}, + Format: "", + }, + }, + "differentHostServerRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "differentHostServerRefs is a list of references to Server objects. The server will be scheduled on a different host than all specified servers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "sameHostServerRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "sameHostServerRefs is a list of references to Server objects. The server will be scheduled on the same host as all specified servers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "query": { + SchemaProps: spec.SchemaProps{ + Description: "query is a conditional statement that results in compute nodes able to host the server.", + Type: []string{"string"}, + Format: "", + }, + }, + "targetCell": { + SchemaProps: spec.SchemaProps{ + Description: "targetCell is a cell name where the server will be placed.", + Type: []string{"string"}, + Format: "", + }, + }, + "differentCell": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "differentCell is a list of cell names where the server should not be placed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "buildNearHostIP": { + SchemaProps: spec.SchemaProps{ + Description: "buildNearHostIP specifies a subnet of compute nodes to host the server. The host IP should be provided in an CIDR format like 10.10.10.10/24.", + Type: []string{"string"}, + Format: "", + }, + }, + "additionalProperties": { + SchemaProps: spec.SchemaProps{ + Description: "additionalProperties is a map of arbitrary key/value pairs that are not validated by Nova.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index 501e39ae2..62aba055f 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -314,15 +314,78 @@ spec: maxItems: 64 type: array x-kubernetes-list-type: atomic - serverGroupRef: - description: |- - serverGroupRef is a reference to a ServerGroup object. The server - will be created in the server group. - maxLength: 253 - minLength: 1 - type: string + schedulerHints: + description: schedulerHints provides hints to the Nova scheduler + for server placement. + properties: + additionalProperties: + additionalProperties: + type: string + description: |- + additionalProperties is a map of arbitrary key/value pairs that are + not validated by Nova. + type: object + buildNearHostIP: + description: |- + buildNearHostIP specifies a subnet of compute nodes to host the server. + The host IP should be provided in an CIDR format like 10.10.10.10/24. + format: cidr + maxLength: 49 + minLength: 1 + type: string + differentCell: + description: |- + differentCell is a list of cell names where the server should not + be placed. + items: + maxLength: 1024 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + differentHostServerRefs: + description: |- + differentHostServerRefs is a list of references to Server objects. + The server will be scheduled on a different host than all specified servers. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + query: + description: |- + query is a conditional statement that results in compute nodes + able to host the server. + maxLength: 1024 + type: string + sameHostServerRefs: + description: |- + sameHostServerRefs is a list of references to Server objects. + The server will be scheduled on the same host as all specified servers. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + serverGroupRef: + description: |- + serverGroupRef is a reference to a ServerGroup object. The server will be + scheduled on a host in the specified server group. + maxLength: 253 + minLength: 1 + type: string + targetCell: + description: targetCell is a cell name where the server will + be placed. + maxLength: 255 + type: string + type: object x-kubernetes-validations: - - message: serverGroupRef is immutable + - message: schedulerHints is immutable rule: self == oldSelf tags: description: tags is a list of tags which will be applied to the diff --git a/config/samples/openstack_v1alpha1_server.yaml b/config/samples/openstack_v1alpha1_server.yaml index 382d6f9b4..0ee8c24ff 100644 --- a/config/samples/openstack_v1alpha1_server.yaml +++ b/config/samples/openstack_v1alpha1_server.yaml @@ -14,8 +14,9 @@ spec: - portRef: server-sample volumes: - volumeRef: server-sample - serverGroupRef: server-sample keypairRef: server-sample + schedulerHints: + serverGroupRef: server-sample availabilityZone: nova tags: - tag1 diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index 9644e95e2..e14c371d1 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -18,6 +18,7 @@ package server import ( "context" + "encoding/json" "fmt" "iter" "maps" @@ -150,6 +151,100 @@ func (actuator serverActuator) ListOSResourcesForImport(ctx context.Context, obj return wrapServers(actuator.osClient.ListServers(ctx, listOpts)), nil } +func (actuator serverActuator) getSchedulerHints(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (servers.SchedulerHintOpts, progress.ReconcileStatus) { + hints := servers.SchedulerHintOpts{} + + if resource.SchedulerHints == nil { + return hints, progress.NewReconcileStatus() + } + + schedHints := resource.SchedulerHints + reconcileStatus := progress.NewReconcileStatus() + + // Resolve ServerGroupRef to server group ID + sg, sgReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + schedHints.ServerGroupRef, "ServerGroup", + func(sg *orcv1alpha1.ServerGroup) bool { + return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(sgReconcileStatus) + if sg.Status.ID != nil { + hints.Group = *sg.Status.ID + } + + // Resolve differentHostServerRefs to server IDs + if len(schedHints.DifferentHostServerRefs) > 0 { + differentHost := make([]string, 0, len(schedHints.DifferentHostServerRefs)) + for i := range schedHints.DifferentHostServerRefs { + ref := &schedHints.DifferentHostServerRefs[i] + server, serverReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + ref, "Server", + func(s *orcv1alpha1.Server) bool { + return s.Status.ID != nil && + s.Status.Resource != nil && + s.Status.Resource.Status == "ACTIVE" + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(serverReconcileStatus) + if server.Status.ID != nil { + differentHost = append(differentHost, *server.Status.ID) + } + } + hints.DifferentHost = differentHost + } + + // Resolve sameHostServerRefs to server IDs + if len(schedHints.SameHostServerRefs) > 0 { + sameHost := make([]string, 0, len(schedHints.SameHostServerRefs)) + for i := range schedHints.SameHostServerRefs { + ref := &schedHints.SameHostServerRefs[i] + server, serverReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + ref, "Server", + func(s *orcv1alpha1.Server) bool { + return s.Status.ID != nil && + s.Status.Resource != nil && + s.Status.Resource.Status == "ACTIVE" + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(serverReconcileStatus) + if server.Status.ID != nil { + sameHost = append(sameHost, *server.Status.ID) + } + } + hints.SameHost = sameHost + } + + if schedHints.Query != "" { + var query []any + if err := json.Unmarshal([]byte(schedHints.Query), &query); err != nil { + return hints, progress.WrapError(orcerrors.Terminal( + orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid scheduler hints query: "+err.Error(), err)) + } + hints.Query = query + } + if schedHints.TargetCell != "" { + hints.TargetCell = schedHints.TargetCell + } + hints.DifferentCell = schedHints.DifferentCell + if schedHints.BuildNearHostIP != nil { + hints.BuildNearHostIP = string(ptr.Deref(schedHints.BuildNearHostIP, "")) + } + if schedHints.AdditionalProperties != nil { + additionalProps := make(map[string]any, len(schedHints.AdditionalProperties)) + for k, v := range schedHints.AdditionalProperties { + additionalProps[k] = v + } + hints.AdditionalProperties = additionalProps + } + + return hints, reconcileStatus +} + func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.Server) (*osResourceT, progress.ReconcileStatus) { resource := obj.Spec.Resource if resource == nil { @@ -232,12 +327,8 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp } } - serverGroup, serverGroupReconcileStatus := dependency.FetchDependency[*orcv1alpha1.ServerGroup]( - ctx, actuator.k8sClient, obj.Namespace, - resource.ServerGroupRef, "ServerGroup", - orcv1alpha1.IsAvailable, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(serverGroupReconcileStatus) + schedulerHints, schedulerHintsReconcileStatus := actuator.getSchedulerHints(ctx, obj, resource) + reconcileStatus = reconcileStatus.WithReconcileStatus(schedulerHintsReconcileStatus) keypair, keypairReconcileStatus := dependency.FetchDependency( ctx, actuator.k8sClient, obj.Namespace, @@ -304,10 +395,6 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp } } - schedulerHints := servers.SchedulerHintOpts{ - Group: ptr.Deref(serverGroup.Status.ID, ""), - } - server, err := actuator.osClient.CreateServer(ctx, createOpts, schedulerHints) // We should require the spec to be updated before retrying a create which returned a non-retryable error diff --git a/internal/controllers/server/controller.go b/internal/controllers/server/controller.go index c83381a9a..b59bc258b 100644 --- a/internal/controllers/server/controller.go +++ b/internal/controllers/server/controller.go @@ -123,14 +123,14 @@ var ( // No deletion guard for server group, because server group can be safely deleted while // referenced by a server serverGroupDependency = dependency.NewDependency[*orcv1alpha1.ServerList, *orcv1alpha1.ServerGroup]( - "spec.resource.serverGroupRef", + "spec.resource.schedulerHints.serverGroupRef", func(server *orcv1alpha1.Server) []string { resource := server.Spec.Resource - if resource == nil || resource.ServerGroupRef == nil { + if resource == nil || resource.SchedulerHints == nil || resource.SchedulerHints.ServerGroupRef == nil { return nil } - return []string{string(*resource.ServerGroupRef)} + return []string{string(*resource.SchedulerHints.ServerGroupRef)} }, ) @@ -179,6 +179,40 @@ var ( }, finalizer, externalObjectFieldOwner, ) + + // No deletion guard for server references in scheduler hints, because they + // are only used on creation for placement decisions + sameHostServerRefDependency = dependency.NewDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Server]( + "spec.resource.schedulerHints.sameHostServerRefs", + func(server *orcv1alpha1.Server) []string { + resource := server.Spec.Resource + if resource == nil || resource.SchedulerHints == nil { + return nil + } + + refs := make([]string, 0, len(resource.SchedulerHints.SameHostServerRefs)) + for _, ref := range resource.SchedulerHints.SameHostServerRefs { + refs = append(refs, string(ref)) + } + return refs + }, + ) + + differentHostServerRefDependency = dependency.NewDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Server]( + "spec.resource.schedulerHints.differentHostServerRefs", + func(server *orcv1alpha1.Server) []string { + resource := server.Spec.Resource + if resource == nil || resource.SchedulerHints == nil { + return nil + } + + refs := make([]string, 0, len(resource.SchedulerHints.DifferentHostServerRefs)) + for _, ref := range resource.SchedulerHints.DifferentHostServerRefs { + refs = append(refs, string(ref)) + } + return refs + }, + ) ) // SetupWithManager sets up the controller with the Manager. @@ -218,6 +252,14 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c if err != nil { return err } + sameHostServerRefWatchEventHandler, err := sameHostServerRefDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + differentHostServerRefWatchEventHandler, err := differentHostServerRefDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). @@ -243,6 +285,12 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c Watches(&orcv1alpha1.KeyPair{}, keypairWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.KeyPair{})), ). + Watches(&orcv1alpha1.Server{}, sameHostServerRefWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Server{})), + ). + Watches(&orcv1alpha1.Server{}, differentHostServerRefWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Server{})), + ). // XXX: This is a general watch on secrets. A general watch on secrets // is undesirable because: // - It requires problematic RBAC @@ -261,6 +309,8 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c volumeDependency.AddToManager(ctx, mgr), bootVolumeDependency.AddToManager(ctx, mgr), keypairDependency.AddToManager(ctx, mgr), + sameHostServerRefDependency.AddToManager(ctx, mgr), + differentHostServerRefDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency), ); err != nil { 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 68c65c73b..14b3d5be7 100644 --- a/internal/controllers/server/tests/server-create-full/00-assert.yaml +++ b/internal/controllers/server/tests/server-create-full/00-assert.yaml @@ -14,6 +14,14 @@ resourceRefs: kind: Port name: server-create-full ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: server-create-full-dummy + ref: portDummy + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Server + name: server-create-full-dummy + ref: serverDummy - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ServerGroup name: server-create-full @@ -35,7 +43,9 @@ resourceRefs: name: server-create-full ref: subnet assertAll: + - celExpr: "serverDummy.status.resource.status == 'ACTIVE'" - celExpr: "server.status.resource.hostID != ''" + - celExpr: "server.status.resource.hostID == serverDummy.status.resource.hostID" - celExpr: "server.status.resource.availabilityZone == 'nova'" - celExpr: "server.status.resource.imageID == image.status.id" - celExpr: "server.status.resource.serverGroups[0] == sg.status.id" 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 6f82c53f2..a06c5e43d 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 @@ -14,6 +14,20 @@ spec: - subnetRef: server-create-full --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: server-create-full-dummy +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: server-create-full + addresses: + - subnetRef: server-create-full +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Volume metadata: name: server-create-full @@ -27,6 +41,21 @@ spec: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Server +metadata: + name: server-create-full-dummy +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + imageRef: server-create-full + flavorRef: server-create-full + ports: + - portRef: server-create-full-dummy +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Server metadata: name: server-create-full spec: @@ -40,8 +69,15 @@ spec: flavorRef: server-create-full ports: - portRef: server-create-full - serverGroupRef: server-create-full keypairRef: server-create-full + schedulerHints: + serverGroupRef: server-create-full + query: '[">=", "$free_ram_mb", 32]' + buildNearHostIP: 10.0.0.0/8 + additionalProperties: + custom_hint: custom_value + sameHostServerRefs: + - server-create-full-dummy volumes: - volumeRef: server-create-full availabilityZone: nova diff --git a/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml b/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml index 101976fcf..ae93fc454 100644 --- a/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml +++ b/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml @@ -87,6 +87,7 @@ spec: flavorRef: server-dependency ports: - portRef: server-dependency - serverGroupRef: server-dependency + schedulerHints: + serverGroupRef: server-dependency userData: secretRef: server-dependency \ No newline at end of file diff --git a/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml b/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml index 5757e4eea..a669f622f 100644 --- a/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml +++ b/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml @@ -27,6 +27,7 @@ spec: flavorRef: server-dependency ports: - portRef: server-dependency - serverGroupRef: server-dependency + schedulerHints: + serverGroupRef: server-dependency userData: secretRef: server-dependency diff --git a/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml b/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml index 4dd1e19b0..45f5348cc 100644 --- a/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml +++ b/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml @@ -38,6 +38,7 @@ spec: flavorRef: server-dependency ports: - portRef: server-dependency - serverGroupRef: server-dependency + schedulerHints: + serverGroupRef: server-dependency userData: secretRef: server-dependency diff --git a/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml b/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml index 6483cf47f..f15e8960e 100644 --- a/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml +++ b/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml @@ -37,6 +37,7 @@ spec: flavorRef: server-dependency ports: - portRef: server-dependency - serverGroupRef: server-dependency + schedulerHints: + serverGroupRef: server-dependency userData: secretRef: server-dependency diff --git a/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml b/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml index bc9196a80..f8b8b4d02 100644 --- a/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml +++ b/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml @@ -35,6 +35,7 @@ spec: flavorRef: server-dependency ports: - portRef: server-dependency - serverGroupRef: server-dependency + schedulerHints: + serverGroupRef: server-dependency userData: secretRef: server-dependency diff --git a/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml b/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml index eb4776259..031ed37ac 100644 --- a/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml +++ b/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml @@ -27,7 +27,8 @@ spec: flavorRef: server-dependency ports: - portRef: server-dependency - serverGroupRef: server-dependency + schedulerHints: + serverGroupRef: server-dependency keypairRef: server-dependency userData: secretRef: server-dependency diff --git a/internal/controllers/server/tests/server-update/00-minimal-resource.yaml b/internal/controllers/server/tests/server-update/00-minimal-resource.yaml index 4a62a151a..95dca9e29 100644 --- a/internal/controllers/server/tests/server-update/00-minimal-resource.yaml +++ b/internal/controllers/server/tests/server-update/00-minimal-resource.yaml @@ -13,4 +13,5 @@ spec: flavorRef: server-update ports: - portRef: server-update - serverGroupRef: server-update \ No newline at end of file + schedulerHints: + serverGroupRef: server-update \ No newline at end of file diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go index 9348ac5c4..3a95e453d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go @@ -32,12 +32,12 @@ type ServerResourceSpecApplyConfiguration struct { UserData *UserDataSpecApplyConfiguration `json:"userData,omitempty"` Ports []ServerPortSpecApplyConfiguration `json:"ports,omitempty"` Volumes []ServerVolumeSpecApplyConfiguration `json:"volumes,omitempty"` - ServerGroupRef *apiv1alpha1.KubernetesNameRef `json:"serverGroupRef,omitempty"` AvailabilityZone *string `json:"availabilityZone,omitempty"` KeypairRef *apiv1alpha1.KubernetesNameRef `json:"keypairRef,omitempty"` Tags []apiv1alpha1.ServerTag `json:"tags,omitempty"` Metadata []ServerMetadataApplyConfiguration `json:"metadata,omitempty"` ConfigDrive *bool `json:"configDrive,omitempty"` + SchedulerHints *ServerSchedulerHintsApplyConfiguration `json:"schedulerHints,omitempty"` } // ServerResourceSpecApplyConfiguration constructs a declarative configuration of the ServerResourceSpec type for use with @@ -112,14 +112,6 @@ func (b *ServerResourceSpecApplyConfiguration) WithVolumes(values ...*ServerVolu return b } -// WithServerGroupRef sets the ServerGroupRef field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the ServerGroupRef field is set to the value of the last call. -func (b *ServerResourceSpecApplyConfiguration) WithServerGroupRef(value apiv1alpha1.KubernetesNameRef) *ServerResourceSpecApplyConfiguration { - b.ServerGroupRef = &value - return b -} - // WithAvailabilityZone sets the AvailabilityZone field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the AvailabilityZone field is set to the value of the last call. @@ -166,3 +158,11 @@ func (b *ServerResourceSpecApplyConfiguration) WithConfigDrive(value bool) *Serv b.ConfigDrive = &value return b } + +// WithSchedulerHints sets the SchedulerHints field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchedulerHints field is set to the value of the last call. +func (b *ServerResourceSpecApplyConfiguration) WithSchedulerHints(value *ServerSchedulerHintsApplyConfiguration) *ServerResourceSpecApplyConfiguration { + b.SchedulerHints = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go new file mode 100644 index 000000000..5fc022118 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go @@ -0,0 +1,118 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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" +) + +// ServerSchedulerHintsApplyConfiguration represents a declarative configuration of the ServerSchedulerHints type for use +// with apply. +type ServerSchedulerHintsApplyConfiguration struct { + ServerGroupRef *apiv1alpha1.KubernetesNameRef `json:"serverGroupRef,omitempty"` + DifferentHostServerRefs []apiv1alpha1.KubernetesNameRef `json:"differentHostServerRefs,omitempty"` + SameHostServerRefs []apiv1alpha1.KubernetesNameRef `json:"sameHostServerRefs,omitempty"` + Query *string `json:"query,omitempty"` + TargetCell *string `json:"targetCell,omitempty"` + DifferentCell []string `json:"differentCell,omitempty"` + BuildNearHostIP *apiv1alpha1.CIDR `json:"buildNearHostIP,omitempty"` + AdditionalProperties map[string]string `json:"additionalProperties,omitempty"` +} + +// ServerSchedulerHintsApplyConfiguration constructs a declarative configuration of the ServerSchedulerHints type for use with +// apply. +func ServerSchedulerHints() *ServerSchedulerHintsApplyConfiguration { + return &ServerSchedulerHintsApplyConfiguration{} +} + +// WithServerGroupRef sets the ServerGroupRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServerGroupRef field is set to the value of the last call. +func (b *ServerSchedulerHintsApplyConfiguration) WithServerGroupRef(value apiv1alpha1.KubernetesNameRef) *ServerSchedulerHintsApplyConfiguration { + b.ServerGroupRef = &value + return b +} + +// WithDifferentHostServerRefs adds the given value to the DifferentHostServerRefs 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 DifferentHostServerRefs field. +func (b *ServerSchedulerHintsApplyConfiguration) WithDifferentHostServerRefs(values ...apiv1alpha1.KubernetesNameRef) *ServerSchedulerHintsApplyConfiguration { + for i := range values { + b.DifferentHostServerRefs = append(b.DifferentHostServerRefs, values[i]) + } + return b +} + +// WithSameHostServerRefs adds the given value to the SameHostServerRefs 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 SameHostServerRefs field. +func (b *ServerSchedulerHintsApplyConfiguration) WithSameHostServerRefs(values ...apiv1alpha1.KubernetesNameRef) *ServerSchedulerHintsApplyConfiguration { + for i := range values { + b.SameHostServerRefs = append(b.SameHostServerRefs, values[i]) + } + return b +} + +// WithQuery sets the Query field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Query field is set to the value of the last call. +func (b *ServerSchedulerHintsApplyConfiguration) WithQuery(value string) *ServerSchedulerHintsApplyConfiguration { + b.Query = &value + return b +} + +// WithTargetCell sets the TargetCell field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TargetCell field is set to the value of the last call. +func (b *ServerSchedulerHintsApplyConfiguration) WithTargetCell(value string) *ServerSchedulerHintsApplyConfiguration { + b.TargetCell = &value + return b +} + +// WithDifferentCell adds the given value to the DifferentCell 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 DifferentCell field. +func (b *ServerSchedulerHintsApplyConfiguration) WithDifferentCell(values ...string) *ServerSchedulerHintsApplyConfiguration { + for i := range values { + b.DifferentCell = append(b.DifferentCell, values[i]) + } + return b +} + +// WithBuildNearHostIP sets the BuildNearHostIP field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the BuildNearHostIP field is set to the value of the last call. +func (b *ServerSchedulerHintsApplyConfiguration) WithBuildNearHostIP(value apiv1alpha1.CIDR) *ServerSchedulerHintsApplyConfiguration { + b.BuildNearHostIP = &value + return b +} + +// WithAdditionalProperties puts the entries into the AdditionalProperties 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 AdditionalProperties field, +// overwriting an existing map entries in AdditionalProperties field with the same key. +func (b *ServerSchedulerHintsApplyConfiguration) WithAdditionalProperties(entries map[string]string) *ServerSchedulerHintsApplyConfiguration { + if b.AdditionalProperties == nil && len(entries) > 0 { + b.AdditionalProperties = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalProperties[k] = v + } + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 419e61894..908f9ba4b 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2855,9 +2855,9 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerPortSpec elementRelationship: atomic - - name: serverGroupRef + - name: schedulerHints type: - scalar: string + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerSchedulerHints - name: tags type: list: @@ -2924,6 +2924,44 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerVolumeStatus elementRelationship: atomic +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerSchedulerHints + map: + fields: + - name: additionalProperties + type: + map: + elementType: + scalar: string + - name: buildNearHostIP + type: + scalar: string + - name: differentCell + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: differentHostServerRefs + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: query + type: + scalar: string + - name: sameHostServerRefs + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: serverGroupRef + type: + scalar: string + - name: targetCell + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerSpec map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 618c6c9b7..ac12250cd 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -354,6 +354,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ServerResourceSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerResourceStatus"): return &apiv1alpha1.ServerResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ServerSchedulerHints"): + return &apiv1alpha1.ServerSchedulerHintsApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerSpec"): return &apiv1alpha1.ServerSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerStatus"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 12e26692e..8b8e2d76b 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -484,6 +484,7 @@ _Validation:_ _Appears in:_ - [HostRoute](#hostroute) - [SecurityGroupRule](#securitygrouprule) +- [ServerSchedulerHints](#serverschedulerhints) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) @@ -2166,6 +2167,7 @@ _Appears in:_ - [ServerBootVolumeSpec](#serverbootvolumespec) - [ServerPortSpec](#serverportspec) - [ServerResourceSpec](#serverresourcespec) +- [ServerSchedulerHints](#serverschedulerhints) - [ServerVolumeSpec](#servervolumespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) @@ -3953,12 +3955,12 @@ _Appears in:_ | `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: \{\}
| +| `schedulerHints` _[ServerSchedulerHints](#serverschedulerhints)_ | schedulerHints provides hints to the Nova scheduler for server placement. | | Optional: \{\}
| #### ServerResourceStatus @@ -3987,6 +3989,29 @@ _Appears in:_ | `configDrive` _boolean_ | configDrive indicates whether the server was booted with a config drive. | | Optional: \{\}
| +#### ServerSchedulerHints + + + +ServerSchedulerHints provides hints to the Nova scheduler for server placement. + + + +_Appears in:_ +- [ServerResourceSpec](#serverresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `serverGroupRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverGroupRef is a reference to a ServerGroup object. The server will be
scheduled on a host in the specified server group. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `differentHostServerRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | differentHostServerRefs is a list of references to Server objects.
The server will be scheduled on a different host than all specified servers. | | MaxItems: 64
MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `sameHostServerRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | sameHostServerRefs is a list of references to Server objects.
The server will be scheduled on the same host as all specified servers. | | MaxItems: 64
MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `query` _string_ | query is a conditional statement that results in compute nodes
able to host the server. | | MaxLength: 1024
Optional: \{\}
| +| `targetCell` _string_ | targetCell is a cell name where the server will be placed. | | MaxLength: 255
Optional: \{\}
| +| `differentCell` _string array_ | differentCell is a list of cell names where the server should not
be placed. | | MaxItems: 64
items:MaxLength: 1024
Optional: \{\}
| +| `buildNearHostIP` _[CIDR](#cidr)_ | buildNearHostIP specifies a subnet of compute nodes to host the server.
The host IP should be provided in an CIDR format like 10.10.10.10/24. | | Format: cidr
MaxLength: 49
MinLength: 1
Optional: \{\}
| +| `additionalProperties` _object (keys:string, values:string)_ | additionalProperties is a map of arbitrary key/value pairs that are
not validated by Nova. | | Optional: \{\}
| + + #### ServerSpec From 92dffa56760f6d5b06a04437419041ccf60a8cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Fri, 17 Apr 2026 17:38:24 +0200 Subject: [PATCH 173/237] Fix api validation tests after rebase --- test/apivalidations/server_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/apivalidations/server_test.go b/test/apivalidations/server_test.go index 360ee9865..9af650f79 100644 --- a/test/apivalidations/server_test.go +++ b/test/apivalidations/server_test.go @@ -157,22 +157,22 @@ var _ = Describe("ORC Server API validations", func() { Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("flavorRef is immutable"))) }) - It("should have immutable serverGroupRef", func(ctx context.Context) { + It("should have immutable schedulerHints", 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")) + WithSchedulerHints(applyconfigv1alpha1.ServerSchedulerHints().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"))) + WithSchedulerHints(applyconfigv1alpha1.ServerSchedulerHints().WithServerGroupRef("sg-b"))) + Expect(applyObj(ctx, server, patch)).To(MatchError(ContainSubstring("schedulerHints is immutable"))) }) It("should have immutable keypairRef", func(ctx context.Context) { From f99e3d5536047be47c8c1ea94ee4459567359261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 23 Apr 2026 13:53:59 +0200 Subject: [PATCH 174/237] Simplify dependency ready filters to use IsAvailable directly Since IsAvailable implies Status.ID is set, the redundant check for Status.ID != nil can be removed. Pass orcv1alpha1.IsAvailable directly as the filter function for the ServerGroup and boot Volume dependencies, consistent with the changes made in all other controllers. --- internal/controllers/server/actuator.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index e14c371d1..f86c50f82 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -162,12 +162,10 @@ func (actuator serverActuator) getSchedulerHints(ctx context.Context, obj *orcv1 reconcileStatus := progress.NewReconcileStatus() // Resolve ServerGroupRef to server group ID - sg, sgReconcileStatus := dependency.FetchDependency( + sg, sgReconcileStatus := dependency.FetchDependency[*orcv1alpha1.ServerGroup]( ctx, actuator.k8sClient, obj.Namespace, schedHints.ServerGroupRef, "ServerGroup", - func(sg *orcv1alpha1.ServerGroup) bool { - return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(sgReconcileStatus) if sg.Status.ID != nil { @@ -274,9 +272,7 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp var blockDevices []servers.BlockDevice if bootFromVolume { bootVolume, bvReconcileStatus := bootVolumeDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(volume *orcv1alpha1.Volume) bool { - return orcv1alpha1.IsAvailable(volume) && volume.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(bvReconcileStatus) From c9a4c1b7f180c3062e0ed6d671a4514dbeef5b98 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Sat, 25 Apr 2026 13:21:22 +0100 Subject: [PATCH 175/237] port: add trustedVIF field --- .github/workflows/e2e.yaml | 4 ++- api/v1alpha1/port_types.go | 12 +++++++ api/v1alpha1/zz_generated.deepcopy.go | 10 ++++++ cmd/models-schema/zz_generated.openapi.go | 14 ++++++++ .../bases/openstack.k-orc.cloud_ports.yaml | 12 +++++++ internal/controllers/port/actuator.go | 25 ++++++++++++- internal/controllers/port/actuator_test.go | 35 +++++++++++++++++++ internal/controllers/port/status.go | 4 +++ .../tests/port-create-sriov/00-assert.yaml | 15 ++++++-- .../port-create-sriov/00-create-resource.yaml | 17 ++++++++- .../port/tests/port-create-sriov/README.md | 2 +- .../port/tests/port-update/00-assert.yaml | 19 ++++++++++ .../port-update/00-minimal-resource.yaml | 15 ++++++++ .../tests/port-update/00-prerequisites.yaml | 2 +- .../port/tests/port-update/01-assert.yaml | 18 ++++++++++ .../port-update/01-updated-resource.yaml | 12 +++++++ .../port/tests/port-update/02-assert.yaml | 20 ++++++++++- internal/osclients/networking.go | 2 ++ .../api/v1alpha1/portresourcespec.go | 9 +++++ .../api/v1alpha1/portresourcestatus.go | 9 +++++ .../applyconfiguration/internal/internal.go | 6 ++++ website/docs/crd-reference.md | 2 ++ 22 files changed, 255 insertions(+), 9 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 9200a5811..f86bd3c55 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -38,8 +38,10 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server,neutron-trunk" + enabled_services: "openstack-cli-server,neutron-trunk,neutron-port-trusted-vif" conf_overrides: | + enable_plugin neutron https://github.com/openstack/neutron ${{ matrix.openstack_version }} + [[post-config|/etc/nova/nova.conf]] [filter_scheduler] enabled_filters = ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter,SameHostFilter,DifferentHostFilter,SimpleCIDRAffinityFilter,JsonFilter diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 2592082fe..f9438abe8 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -210,6 +210,12 @@ type PortResourceSpec struct { // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="hostID is immutable" HostID *HostID `json:"hostID,omitempty"` //nolint:kubeapilinter // HostID provides both raw ID and ServerRef options + + // trustedVIF indicates whether the VF for the port will become + // trusted by physical function to perform some privileged + // operations. Only admin users can create ports with this field. + // +optional + TrustedVIF *bool `json:"trustedVIF,omitempty"` } type PortResourceStatus struct { @@ -307,6 +313,12 @@ type PortResourceStatus struct { // +optional HostID string `json:"hostID,omitempty"` + // trustedVIF indicates whether the VF for the port will become + // trusted by physical function to perform some privileged + // operations. + // +optional + TrustedVIF *bool `json:"trustedVIF,omitempty"` + NeutronStatusMetadata `json:",inline"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 05d5be17c..123437355 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3334,6 +3334,11 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { *out = new(HostID) **out = **in } + if in.TrustedVIF != nil { + in, out := &in.TrustedVIF, &out.TrustedVIF + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortResourceSpec. @@ -3384,6 +3389,11 @@ func (in *PortResourceStatus) DeepCopyInto(out *PortResourceStatus) { *out = new(bool) **out = **in } + if in.TrustedVIF != nil { + in, out := &in.TrustedVIF, &out.TrustedVIF + *out = new(bool) + **out = **in + } in.NeutronStatusMetadata.DeepCopyInto(&out.NeutronStatusMetadata) } diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 4a6dfd9f8..3e0331355 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -6272,6 +6272,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID"), }, }, + "trustedVIF": { + SchemaProps: spec.SchemaProps{ + Description: "trustedVIF indicates whether the VF for the port will become trusted by physical function to perform some privileged operations. Only admin users can create ports with this field.", + Type: []string{"boolean"}, + Format: "", + }, + }, }, Required: []string{"networkRef"}, }, @@ -6456,6 +6463,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref Format: "", }, }, + "trustedVIF": { + SchemaProps: spec.SchemaProps{ + Description: "trustedVIF indicates whether the VF for the port will become trusted by physical function to perform some privileged operations.", + Type: []string{"boolean"}, + 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 9018183f8..c6ad9de90 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -401,6 +401,12 @@ spec: maxItems: 64 type: array x-kubernetes-list-type: set + trustedVIF: + description: |- + trustedVIF indicates whether the VF for the port will become + trusted by physical function to perform some privileged + operations. Only admin users can create ports with this field. + type: boolean vnicType: description: |- vnicType specifies the type of vNIC which this port should be @@ -650,6 +656,12 @@ spec: maxItems: 64 type: array x-kubernetes-list-type: atomic + trustedVIF: + description: |- + trustedVIF indicates whether the VF for the port will become + trusted by physical function to perform some privileged + operations. + type: boolean updatedAt: description: updatedAt shows the date and time when the resource was updated. The date and time stamp format is ISO 8601 diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 393aac196..0b525a903 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -25,6 +25,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsbinding" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portstrustedvif" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" @@ -318,7 +319,14 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, fmt.Sprintf("Invalid value %s", resource.PortSecurity))) } - osResource, err := actuator.osClient.CreatePort(ctx, &portSecurityOpts) + portTrustedOpts := portstrustedvif.PortCreateOptsExt{ + CreateOptsBuilder: portSecurityOpts, + } + if resource.TrustedVIF != nil { + portTrustedOpts.PortTrustedVIF = resource.TrustedVIF + } + + osResource, err := actuator.osClient.CreatePort(ctx, &portTrustedOpts) if err != nil { // We should require the spec to be updated before retrying a create which returned a conflict if orcerrors.IsConflict(err) { @@ -416,6 +424,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource) updateOpts = handlePortSecurityUpdate(updateOpts, resource, osResource) + updateOpts = handlePortTrustedVIFUpdate(updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) if err != nil { @@ -587,6 +596,20 @@ func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSp } } +func handlePortTrustedVIFUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT) ports.UpdateOptsBuilder { + trusted := resource.TrustedVIF + if trusted != nil { + if osResource.PortTrustedVIF == nil || *trusted != *osResource.PortTrustedVIF { + updateOpts = portstrustedvif.PortUpdateOptsExt{ + UpdateOptsBuilder: updateOpts, + PortTrustedVIF: trusted, + } + } + } + + return updateOpts +} + type portHelperFactory struct{} var _ helperFactory = portHelperFactory{} diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index 1da3d948e..cf31483a9 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -5,6 +5,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsbinding" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portstrustedvif" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" osclients "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" @@ -435,3 +436,37 @@ func TestHandleAdminStateUpUpdate(t *testing.T) { }) } } + +func TestHandleTrustedVIFUpdate(t *testing.T) { + testCases := []struct { + name string + newValue *bool + existingValue *bool + expectChange bool + }{ + {name: "Enabled when the value is not set", newValue: ptr.To(true), existingValue: nil, expectChange: true}, + {name: "Enabled when was disabled", newValue: ptr.To(true), existingValue: ptr.To(false), expectChange: true}, + {name: "Disabled when was enabled", newValue: ptr.To(false), existingValue: ptr.To(true), expectChange: true}, + {name: "Keep the existing value if newValue is not set", newValue: nil, existingValue: ptr.To(true), expectChange: false}, + {name: "Keep the existing value when they are the same (true)", newValue: ptr.To(true), existingValue: ptr.To(true), expectChange: false}, + {name: "Keep the existing value when they are the same (false)", newValue: ptr.To(false), existingValue: ptr.To(false), expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.PortResourceSpec{TrustedVIF: tt.newValue} + osResource := &osclients.PortExt{ + PortTrustedVIFExt: portstrustedvif.PortTrustedVIFExt{ + PortTrustedVIF: tt.existingValue, + }, + } + + updateOpts := handlePortTrustedVIFUpdate(&ports.UpdateOpts{}, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("expected needsUpdate=%v, got %v", tt.expectChange, got) + } + }) + } +} diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index 379e91e70..9a8372569 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -104,5 +104,9 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou resourceStatus.WithFixedIPs(fixedIPs...) } + if osResource.PortTrustedVIF != nil { + resourceStatus.WithTrustedVIF(*osResource.PortTrustedVIF) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/port/tests/port-create-sriov/00-assert.yaml b/internal/controllers/port/tests/port-create-sriov/00-assert.yaml index 3277dfaea..3dc6e78be 100644 --- a/internal/controllers/port/tests/port-create-sriov/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-sriov/00-assert.yaml @@ -15,17 +15,26 @@ status: tags: - tag1 --- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-create-sriov-admin +status: + resource: + name: port-create-sriov-admin + trustedVIF: true +--- apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: port name: port-create-sriov - ref: port + ref: port - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: subnet name: port-create-sriov - ref: subnet + ref: subnet assertAll: - celExpr: "port.status.id != ''" - celExpr: "port.status.resource.createdAt != ''" @@ -35,4 +44,4 @@ assertAll: - celExpr: "port.status.resource.fixedIPs[0].subnetID == subnet.status.id" - celExpr: "port.status.resource.fixedIPs[0].ip == '192.168.155.122'" - celExpr: "!has(port.status.resource.allowedAddressPairs)" - - celExpr: "!has(port.status.resource.securityGroups)" \ No newline at end of file + - celExpr: "!has(port.status.resource.securityGroups)" diff --git a/internal/controllers/port/tests/port-create-sriov/00-create-resource.yaml b/internal/controllers/port/tests/port-create-sriov/00-create-resource.yaml index 69e968816..3318f0ce8 100644 --- a/internal/controllers/port/tests/port-create-sriov/00-create-resource.yaml +++ b/internal/controllers/port/tests/port-create-sriov/00-create-resource.yaml @@ -43,4 +43,19 @@ spec: addresses: - subnetRef: port-create-sriov ip: 192.168.155.122 - vnicType: direct \ No newline at end of file + vnicType: direct +--- +# This port is intended to be used to update fields where policies +# enforce its mutability only by admins. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-create-sriov-admin +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: port-create-sriov + trustedVIF: true diff --git a/internal/controllers/port/tests/port-create-sriov/README.md b/internal/controllers/port/tests/port-create-sriov/README.md index 492044bb6..05d6e4d0a 100644 --- a/internal/controllers/port/tests/port-create-sriov/README.md +++ b/internal/controllers/port/tests/port-create-sriov/README.md @@ -2,7 +2,7 @@ ## Step 00 -Create a port with vnic type direct and port security disabled, and verify that the observed state corresponds to the spec. +Create two ports: one with vnic type direct and port security disabled, and another with admin credentials, so that we can use fields which are enforced by policies, 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/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index 9a20be882..4987674c3 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -36,3 +36,22 @@ 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 + revisionNumber: 1 + trustedVIF: true + 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..3a1d6d2e9 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,18 @@ 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 to update fields where policies +# enforce its mutability only by admins. +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 + trustedVIF: true diff --git a/internal/controllers/port/tests/port-update/00-prerequisites.yaml b/internal/controllers/port/tests/port-update/00-prerequisites.yaml index def6c7a09..ee6959c8b 100644 --- a/internal/controllers/port/tests/port-update/00-prerequisites.yaml +++ b/internal/controllers/port/tests/port-update/00-prerequisites.yaml @@ -40,4 +40,4 @@ spec: cloudName: openstack secretName: openstack-clouds resource: - name: port-update \ No newline at end of file + name: 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 37a622ec7..ee4510e2c 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -49,3 +49,21 @@ status: - type: Progressing status: "False" reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: port-update-admin +status: + resource: + name: port-update-admin + trustedVIF: false + 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/01-updated-resource.yaml b/internal/controllers/port/tests/port-update/01-updated-resource.yaml index 2ad77cb94..b86771ba2 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -20,3 +20,15 @@ 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: + trustedVIF: false diff --git a/internal/controllers/port/tests/port-update/02-assert.yaml b/internal/controllers/port/tests/port-update/02-assert.yaml index 314caa5b0..a3e211d75 100644 --- a/internal/controllers/port/tests/port-update/02-assert.yaml +++ b/internal/controllers/port/tests/port-update/02-assert.yaml @@ -35,4 +35,22 @@ status: - type: Progressing message: OpenStack resource is up to date 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: + name: port-update-admin + trustedVIF: true + 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/osclients/networking.go b/internal/osclients/networking.go index 8696cdbec..088c9799c 100644 --- a/internal/osclients/networking.go +++ b/internal/osclients/networking.go @@ -32,6 +32,7 @@ import ( "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/mtu" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsbinding" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity" + "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portstrustedvif" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/provider" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/groups" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/security/rules" @@ -56,6 +57,7 @@ type PortExt struct { ports.Port portsecurity.PortSecurityExt portsbinding.PortsBindingExt + portstrustedvif.PortTrustedVIFExt } type NetworkClient interface { diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index b1c4a2955..839699f3a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -38,6 +38,7 @@ type PortResourceSpecApplyConfiguration struct { ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` MACAddress *string `json:"macAddress,omitempty"` HostID *HostIDApplyConfiguration `json:"hostID,omitempty"` + TrustedVIF *bool `json:"trustedVIF,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -163,3 +164,11 @@ func (b *PortResourceSpecApplyConfiguration) WithHostID(value *HostIDApplyConfig b.HostID = value return b } + +// WithTrustedVIF sets the TrustedVIF field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustedVIF field is set to the value of the last call. +func (b *PortResourceSpecApplyConfiguration) WithTrustedVIF(value bool) *PortResourceSpecApplyConfiguration { + b.TrustedVIF = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go index 21ad2725e..6557cb5a2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go @@ -42,6 +42,7 @@ type PortResourceStatusApplyConfiguration struct { VNICType *string `json:"vnicType,omitempty"` PortSecurityEnabled *bool `json:"portSecurityEnabled,omitempty"` HostID *string `json:"hostID,omitempty"` + TrustedVIF *bool `json:"trustedVIF,omitempty"` NeutronStatusMetadataApplyConfiguration `json:",inline"` } @@ -201,6 +202,14 @@ func (b *PortResourceStatusApplyConfiguration) WithHostID(value string) *PortRes return b } +// WithTrustedVIF sets the TrustedVIF field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustedVIF field is set to the value of the last call. +func (b *PortResourceStatusApplyConfiguration) WithTrustedVIF(value bool) *PortResourceStatusApplyConfiguration { + b.TrustedVIF = &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 908f9ba4b..137a503f3 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1779,6 +1779,9 @@ var schemaYAML = typed.YAMLObject(`types: elementType: scalar: string elementRelationship: associative + - name: trustedVIF + type: + scalar: boolean - name: vnicType type: scalar: string @@ -1851,6 +1854,9 @@ var schemaYAML = typed.YAMLObject(`types: elementType: scalar: string elementRelationship: atomic + - name: trustedVIF + type: + scalar: boolean - name: updatedAt type: namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 8b8e2d76b..5d70f581f 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2739,6 +2739,7 @@ _Appears in:_ | `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: \{\}
| +| `trustedVIF` _boolean_ | trustedVIF indicates whether the VF for the port will become
trusted by physical function to perform some privileged
operations. Only admin users can create ports with this field. | | Optional: \{\}
| #### PortResourceStatus @@ -2771,6 +2772,7 @@ _Appears in:_ | `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: \{\}
| +| `trustedVIF` _boolean_ | trustedVIF indicates whether the VF for the port will become
trusted by physical function to perform some privileged
operations. | | 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: \{\}
| From 8ec868c46f1f8c341460a0d2ea0fd777f0437f87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:50:44 +0000 Subject: [PATCH 176/237] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 5 updates Bumps the all-go-mod-patch-and-minor group with 4 updates in the / directory: [k8s.io/api](https://github.com/kubernetes/api), [k8s.io/client-go](https://github.com/kubernetes/client-go), [k8s.io/code-generator](https://github.com/kubernetes/code-generator) and [sigs.k8s.io/structured-merge-diff/v6](https://github.com/kubernetes-sigs/structured-merge-diff). Updates `k8s.io/api` from 0.34.6 to 0.34.7 - [Commits](https://github.com/kubernetes/api/compare/v0.34.6...v0.34.7) Updates `k8s.io/apimachinery` from 0.34.6 to 0.34.7 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.6...v0.34.7) Updates `k8s.io/client-go` from 0.34.6 to 0.34.7 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.6...v0.34.7) Updates `k8s.io/code-generator` from 0.34.6 to 0.34.7 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.6...v0.34.7) Updates `sigs.k8s.io/structured-merge-diff/v6` from 6.3.2 to 6.4.0 - [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.2...v6.4.0) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.34.7 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.7 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.7 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.7 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.4.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 | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 9ea974d27..40006fec8 100644 --- a/go.mod +++ b/go.mod @@ -13,15 +13,15 @@ require ( github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 golang.org/x/text v0.36.0 - 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/api v0.34.7 + k8s.io/apimachinery v0.34.7 + k8s.io/client-go v0.34.7 + k8s.io/code-generator v0.34.7 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.2 + sigs.k8s.io/structured-merge-diff/v6 v6.4.0 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 11a89909d..959a64dee 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.6 h1:0ReeOHQfV9SwQ8CMOHkPbM/GscIT3gN2qh463TOEEk4= -k8s.io/api v0.34.6/go.mod h1:u6eOg5ckbO2DUKiyVp7mUMVIA+qZZdW2oyKDhs8nXec= +k8s.io/api v0.34.7 h1:186BQYhLYaaU0jhC4/uC3s4cQ+YSk0BlazRnEldXiqY= +k8s.io/api v0.34.7/go.mod h1:2qet0m0IfHSwXp+RLnunTWADUjy6qaXDa4PdeUnTJ9s= 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.6 h1:Y/ZNX0Mf1E+CT8clgFzLIkOhkbRLTSHqv6+eJnMJaoQ= -k8s.io/apimachinery v0.34.6/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.7 h1:nkAtvxByvBFSyIyOTrFKcy6pgQ4G1NCKsypWArLdNSY= +k8s.io/apimachinery v0.34.7/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= 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.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/client-go v0.34.7 h1:zxksYcHi4DMv6HfjMbHTfcV3jYZh7BY3yH18maoRxsA= +k8s.io/client-go v0.34.7/go.mod h1:GS3K60t9FI20+l3cvB/EfDXyD+JoKr3suIjSGISQLDc= +k8s.io/code-generator v0.34.7 h1:JSFAiGIcMnDLdUilaP3ZSXshHpPH1Elogw6wHpxeNHI= +k8s.io/code-generator v0.34.7/go.mod h1:GbTJ/TjHYE2WWDgV+9q1VTI4UkU5wGLnD2koZW0mkPY= 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= @@ -301,7 +301,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.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0/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 b3c1ede200f2cb5f669cd1cd6ac10a63151d7a8c Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Wed, 11 Feb 2026 21:49:27 +0000 Subject: [PATCH 177/237] port: add ValueSpecs field --- api/v1alpha1/port_types.go | 28 ++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 27 ++++++++++ cmd/models-schema/zz_generated.openapi.go | 52 ++++++++++++++++++- .../bases/openstack.k-orc.cloud_ports.yaml | 35 +++++++++++++ internal/controllers/port/actuator.go | 10 ++++ .../tests/port-create-full/00-assert.yaml | 4 +- .../port/tests/port-create-full/README.md | 5 ++ .../api/v1alpha1/portresourcespec.go | 14 +++++ .../api/v1alpha1/portvaluespec.go | 48 +++++++++++++++++ .../applyconfiguration/internal/internal.go | 17 ++++++ pkg/clients/applyconfiguration/utils.go | 2 + test/apivalidations/port_test.go | 24 +++++++++ website/docs/crd-reference.md | 18 +++++++ 13 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/portvaluespec.go diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index f9438abe8..f743acd16 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -121,6 +121,19 @@ type FixedIPStatus struct { SubnetID string `json:"subnetID,omitempty"` } +type PortValueSpec struct { + // key is the name of the Neutron API extension parameter. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +required + Key string `json:"key,omitempty"` + + // value is the value of the Neutron API extension parameter. + // +kubebuilder:validation:MaxLength:=255 + // +required + Value *string `json:"value,omitempty"` +} + // +kubebuilder:validation:XValidation:rule="has(self.portSecurity) && self.portSecurity == 'Disabled' ? !has(self.securityGroupRefs) : true",message="securityGroupRefs must be empty when portSecurity is set to Disabled" // +kubebuilder:validation:XValidation:rule="has(self.portSecurity) && self.portSecurity == 'Disabled' ? !has(self.allowedAddressPairs) : true",message="allowedAddressPairs must be empty when portSecurity is set to Disabled" type PortResourceSpec struct { @@ -216,6 +229,21 @@ type PortResourceSpec struct { // operations. Only admin users can create ports with this field. // +optional TrustedVIF *bool `json:"trustedVIF,omitempty"` + + // valueSpecs are extra parameters to include in the API request + // with OpenStack. This is an extension point for the API, so what + // they do and if they are supported, depends on the specific + // OpenStack implementation. This was meant to work similar to the + // property on Heat port resource. Since this depends on the + // underlying implementation, we can't predict its fields, and + // therefore, we don't know how to reconcile them in advance. Use + // this field wisely and be aware of the expected behavior. + // +kubebuilder:validation:MaxItems:=128 + // +listType=map + // +listMapKey=key + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="valueSpecs is immutable" + ValueSpecs []PortValueSpec `json:"valueSpecs,omitempty"` } type PortResourceStatus struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 123437355..09694efa4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3339,6 +3339,13 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { *out = new(bool) **out = **in } + if in.ValueSpecs != nil { + in, out := &in.ValueSpecs, &out.ValueSpecs + *out = make([]PortValueSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortResourceSpec. @@ -3470,6 +3477,26 @@ func (in *PortStatus) DeepCopy() *PortStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PortValueSpec) DeepCopyInto(out *PortValueSpec) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortValueSpec. +func (in *PortValueSpec) DeepCopy() *PortValueSpec { + if in == nil { + return nil + } + out := new(PortValueSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Project) DeepCopyInto(out *Project) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3e0331355..3e7cff7c6 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -148,6 +148,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortStatus": schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortValueSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PortValueSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Project": schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectImport": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectImport(ref), @@ -6279,12 +6280,34 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c Format: "", }, }, + "valueSpecs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "key", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "valueSpecs are extra parameters to include in the API request with OpenStack. This is an extension point for the API, so what they do and if they are supported, depends on the specific OpenStack implementation. This was meant to work similar to the property on Heat port resource. Since this depends on the underlying implementation, we can't predict its fields, and therefore, we don't know how to reconcile them in advance. Use this field wisely and be aware of the expected behavior.", + 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.PortValueSpec"), + }, + }, + }, + }, + }, }, Required: []string{"networkRef"}, }, }, 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.HostID"}, + "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", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortValueSpec"}, } } @@ -6597,6 +6620,33 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref common. } } +func schema_openstack_resource_controller_v2_api_v1alpha1_PortValueSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the name of the Neutron API extension parameter.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the value of the Neutron API extension parameter.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Project(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index c6ad9de90..8c38158c6 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -407,6 +407,41 @@ spec: trusted by physical function to perform some privileged operations. Only admin users can create ports with this field. type: boolean + valueSpecs: + description: |- + valueSpecs are extra parameters to include in the API request + with OpenStack. This is an extension point for the API, so what + they do and if they are supported, depends on the specific + OpenStack implementation. This was meant to work similar to the + property on Heat port resource. Since this depends on the + underlying implementation, we can't predict its fields, and + therefore, we don't know how to reconcile them in advance. Use + this field wisely and be aware of the expected behavior. + items: + properties: + key: + description: key is the name of the Neutron API extension + parameter. + maxLength: 255 + minLength: 1 + type: string + value: + description: value is the value of the Neutron API extension + parameter. + maxLength: 255 + type: string + required: + - key + - value + type: object + maxItems: 128 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: valueSpecs is immutable + rule: self == oldSelf vnicType: description: |- vnicType specifies the type of vNIC which this port should be diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 0b525a903..3b927e2a9 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -239,6 +239,15 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha return nil, reconcileStatus } + var valueSpecs *map[string]string + if len(resource.ValueSpecs) > 0 { + vs := make(map[string]string, len(resource.ValueSpecs)) + for _, valueSpec := range resource.ValueSpecs { + vs[valueSpec.Key] = *valueSpec.Value + } + valueSpecs = &vs + } + createOpts := ports.CreateOpts{ NetworkID: *network.Status.ID, Name: getResourceName(obj), @@ -246,6 +255,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha ProjectID: projectID, AdminStateUp: resource.AdminStateUp, MACAddress: resource.MACAddress, + ValueSpecs: valueSpecs, } if len(resource.AllowedAddressPairs) > 0 { 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 f026eea4a..f4ae02435 100644 --- a/internal/controllers/port/tests/port-create-full/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-full/00-assert.yaml @@ -30,11 +30,11 @@ resourceRefs: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: subnet name: port-create-full - ref: subnet + ref: subnet - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: securitygroup name: port-create-full - ref: sg + ref: sg - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: project name: port-create-full diff --git a/internal/controllers/port/tests/port-create-full/README.md b/internal/controllers/port/tests/port-create-full/README.md index b59db2e92..39fdd3351 100644 --- a/internal/controllers/port/tests/port-create-full/README.md +++ b/internal/controllers/port/tests/port-create-full/README.md @@ -4,6 +4,11 @@ Create a port using all available fields, and verify that the observed state corresponds to the spec. +We're omitting the `ValueSpecs` field on purpose because we can't +reliably test it, since the key-value pairs in this structure depend +solely on the underlying OpenStack implementation, and thus the added +fields are unpredictable. + Also validate that the OpenStack resource uses the name from the spec when it is specified. ## Reference diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index 839699f3a..4bdf68bdb 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -39,6 +39,7 @@ type PortResourceSpecApplyConfiguration struct { MACAddress *string `json:"macAddress,omitempty"` HostID *HostIDApplyConfiguration `json:"hostID,omitempty"` TrustedVIF *bool `json:"trustedVIF,omitempty"` + ValueSpecs []PortValueSpecApplyConfiguration `json:"valueSpecs,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -172,3 +173,16 @@ func (b *PortResourceSpecApplyConfiguration) WithTrustedVIF(value bool) *PortRes b.TrustedVIF = &value return b } + +// WithValueSpecs adds the given value to the ValueSpecs 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 ValueSpecs field. +func (b *PortResourceSpecApplyConfiguration) WithValueSpecs(values ...*PortValueSpecApplyConfiguration) *PortResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithValueSpecs") + } + b.ValueSpecs = append(b.ValueSpecs, *values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portvaluespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portvaluespec.go new file mode 100644 index 000000000..c4d305192 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portvaluespec.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 + +// PortValueSpecApplyConfiguration represents a declarative configuration of the PortValueSpec type for use +// with apply. +type PortValueSpecApplyConfiguration struct { + Key *string `json:"key,omitempty"` + Value *string `json:"value,omitempty"` +} + +// PortValueSpecApplyConfiguration constructs a declarative configuration of the PortValueSpec type for use with +// apply. +func PortValueSpec() *PortValueSpecApplyConfiguration { + return &PortValueSpecApplyConfiguration{} +} + +// 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 *PortValueSpecApplyConfiguration) WithKey(value string) *PortValueSpecApplyConfiguration { + 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 *PortValueSpecApplyConfiguration) WithValue(value string) *PortValueSpecApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 137a503f3..b51e2a458 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1782,6 +1782,14 @@ var schemaYAML = typed.YAMLObject(`types: - name: trustedVIF type: scalar: boolean + - name: valueSpecs + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PortValueSpec + elementRelationship: associative + keys: + - key - name: vnicType type: scalar: string @@ -1899,6 +1907,15 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PortResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PortValueSpec + map: + fields: + - name: key + type: + scalar: string + - name: value + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Project map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index ac12250cd..73e66b28b 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -246,6 +246,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.PortSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("PortStatus"): return &apiv1alpha1.PortStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PortValueSpec"): + return &apiv1alpha1.PortValueSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Project"): return &apiv1alpha1.ProjectApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ProjectFilter"): diff --git a/test/apivalidations/port_test.go b/test/apivalidations/port_test.go index 4d84531c5..b8fb5c074 100644 --- a/test/apivalidations/port_test.go +++ b/test/apivalidations/port_test.go @@ -174,6 +174,30 @@ var _ = Describe("ORC Port API validations", func() { Expect(applyObj(ctx, port, patch)).To(MatchError(ContainSubstring("hostID is immutable"))) }) + It("should not allow valueSpecs to be modified", func(ctx context.Context) { + port := portStub(namespace) + patch := basePortPatch(port) + patch.Spec.WithResource(applyconfigv1alpha1.PortResourceSpec(). + WithNetworkRef(networkName). + WithValueSpecs(applyconfigv1alpha1.PortValueSpec().WithKey("test-key").WithValue("test-value"))) + Expect(applyObj(ctx, port, patch)).To(Succeed()) + + patch.Spec.WithResource(applyconfigv1alpha1.PortResourceSpec(). + WithNetworkRef(networkName). + WithValueSpecs(applyconfigv1alpha1.PortValueSpec().WithKey("test-key").WithValue("test-value-updated"))) + Expect(applyObj(ctx, port, patch)).To(MatchError(ContainSubstring("valueSpecs is immutable"))) + }) + + It("should not allow valueSpecs to have duplicate keys", func(ctx context.Context) { + port := portStub(namespace) + patch := basePortPatch(port) + patch.Spec.WithResource(applyconfigv1alpha1.PortResourceSpec(). + WithNetworkRef(networkName). + WithValueSpecs(applyconfigv1alpha1.PortValueSpec().WithKey("test-key").WithValue("test-value-1")). + WithValueSpecs(applyconfigv1alpha1.PortValueSpec().WithKey("test-key").WithValue("test-value-2"))) + Expect(applyObj(ctx, port, patch)).To(MatchError(ContainSubstring("duplicate entries for key"))) + }) + // 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. diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 5d70f581f..756e1f09e 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2740,6 +2740,7 @@ _Appears in:_ | `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: \{\}
| | `trustedVIF` _boolean_ | trustedVIF indicates whether the VF for the port will become
trusted by physical function to perform some privileged
operations. Only admin users can create ports with this field. | | Optional: \{\}
| +| `valueSpecs` _[PortValueSpec](#portvaluespec) array_ | valueSpecs are extra parameters to include in the API request
with OpenStack. This is an extension point for the API, so what
they do and if they are supported, depends on the specific
OpenStack implementation. This was meant to work similar to the
property on Heat port resource. Since this depends on the
underlying implementation, we can't predict its fields, and
therefore, we don't know how to reconcile them in advance. Use
this field wisely and be aware of the expected behavior. | | MaxItems: 128
Optional: \{\}
| #### PortResourceStatus @@ -2835,6 +2836,23 @@ _Appears in:_ | `resource` _[PortResourceStatus](#portresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +#### PortValueSpec + + + + + + + +_Appears in:_ +- [PortResourceSpec](#portresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `key` _string_ | key is the name of the Neutron API extension parameter. | | MaxLength: 255
MinLength: 1
Required: \{\}
| +| `value` _string_ | value is the value of the Neutron API extension parameter. | | MaxLength: 255
Required: \{\}
| + + #### Project From 000e8bae9aa5ce6466863bad66613409b2e4fbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 30 Apr 2026 11:51:00 +0200 Subject: [PATCH 178/237] Use IsRetryable instead of IsConflict for terminal errors Rewrite orcerrors.IsRetryable and replace all uses of orcerrors.IsConflict with !orcerrors.IsRetryable when classifying errors as terminal in create and update paths. IsConflict only caught HTTP 409 responses, but non-HTTP errors from gophercloud (e.g. client-side validation such as banned value_spec keys) would cause infinite retries. The rewritten IsRetryable returns true for more errors that may succeed when retried without changes to the spec: - HTTP error responses other than 409 are retryable (some 4xx errors like 400 Bad Request can be transient, e.g. when a dependency is not yet ready in OpenStack) - HTTP 409 Conflict is not retryable - Non-HTTP errors from gophercloud are not retryable The check is now nested inside an `if err != nil` guard for clarity and safety, matching the pattern already used in CreateResource paths. Also add godoc comments to all public functions in the errors package. Closes #241 --- .agents/skills/new-controller/patterns.md | 23 +++++++++++------- .agents/skills/update-controller/SKILL.md | 23 +++++++++++------- AGENTS.md | 14 ++++++----- .../data/controller/actuator.go.template | 9 +++---- internal/controllers/addressscope/actuator.go | 8 +++---- internal/controllers/domain/actuator.go | 8 +++---- internal/controllers/endpoint/actuator.go | 8 +++---- internal/controllers/floatingip/actuator.go | 14 +++++------ internal/controllers/group/actuator.go | 8 +++---- internal/controllers/image/actuator.go | 14 ++++++----- internal/controllers/network/actuator.go | 10 ++++---- internal/controllers/port/actuator.go | 12 ++++------ internal/controllers/project/actuator.go | 7 ++++-- internal/controllers/role/actuator.go | 8 +++---- internal/controllers/router/actuator.go | 14 +++++------ .../controllers/securitygroup/actuator.go | 10 ++++---- internal/controllers/server/actuator.go | 6 ++--- internal/controllers/service/actuator.go | 8 +++---- internal/controllers/subnet/actuator.go | 16 +++++-------- internal/controllers/trunk/actuator.go | 12 ++++------ internal/controllers/user/actuator.go | 14 +++++------ internal/controllers/volume/actuator.go | 8 +++---- internal/controllers/volumetype/actuator.go | 8 +++---- internal/util/errors/errors.go | 24 +++++++++++++++---- 24 files changed, 142 insertions(+), 144 deletions(-) diff --git a/.agents/skills/new-controller/patterns.md b/.agents/skills/new-controller/patterns.md index 10b6e84d6..e26c60945 100644 --- a/.agents/skills/new-controller/patterns.md +++ b/.agents/skills/new-controller/patterns.md @@ -86,17 +86,22 @@ Distinguish between errors that can be retried vs those requiring user action. | **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) +// Terminal on create: User must fix the spec +if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(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) +// Terminal on update: Treat as terminal (spec likely conflicts with existing state) +if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration updating resource: "+err.Error(), err) + } + return progress.WrapError(err) } ``` diff --git a/.agents/skills/update-controller/SKILL.md b/.agents/skills/update-controller/SKILL.md index 4389af882..e7d59fe0a 100644 --- a/.agents/skills/update-controller/SKILL.md +++ b/.agents/skills/update-controller/SKILL.md @@ -218,17 +218,22 @@ See also `@.agents/skills/new-controller/patterns.md` for more details on this p 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) +// Terminal on create: Invalid configuration - user must fix spec +if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) } -return 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) +// Terminal on update: +if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration updating resource: "+err.Error(), err) + } + return progress.WrapError(err) } ``` diff --git a/AGENTS.md b/AGENTS.md index d64084d55..7795e08f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -89,15 +89,17 @@ 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 +- **Non-recoverable errors** (409 Conflict, non-HTTP gophercloud errors): Wrap with `orcerrors.Terminal()` - no retry ```go -// Terminal error example -if !orcerrors.IsRetryable(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "invalid configuration: "+err.Error(), err) +// Non-recoverable error example +if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration: "+err.Error(), err) + } + return nil, progress.WrapError(err) } -return nil, progress.WrapError(err) ``` ## Dependencies diff --git a/cmd/scaffold-controller/data/controller/actuator.go.template b/cmd/scaffold-controller/data/controller/actuator.go.template index 50d4d65e1..1eca3abf3 100644 --- a/cmd/scaffold-controller/data/controller/actuator.go.template +++ b/cmd/scaffold-controller/data/controller/actuator.go.template @@ -184,7 +184,6 @@ func (actuator {{ .PackageName }}Actuator) CreateResource(ctx context.Context, o osResource, err := actuator.osClient.Create{{ .Kind }}(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) } @@ -232,12 +231,10 @@ func (actuator {{ .PackageName }}Actuator) updateResource(ctx context.Context, o _, err = actuator.osClient.Update{{ .Kind }}(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/addressscope/actuator.go b/internal/controllers/addressscope/actuator.go index 14fb642bc..6c75a4829 100644 --- a/internal/controllers/addressscope/actuator.go +++ b/internal/controllers/addressscope/actuator.go @@ -195,12 +195,10 @@ func (actuator addressscopeActuator) updateResource(ctx context.Context, obj orc _, 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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/domain/actuator.go b/internal/controllers/domain/actuator.go index 968ddc990..d9653bcf0 100644 --- a/internal/controllers/domain/actuator.go +++ b/internal/controllers/domain/actuator.go @@ -144,12 +144,10 @@ func (actuator domainActuator) updateResource(ctx context.Context, obj orcObject _, err = actuator.osClient.UpdateDomain(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/endpoint/actuator.go b/internal/controllers/endpoint/actuator.go index 7c74ee14e..57d62f187 100644 --- a/internal/controllers/endpoint/actuator.go +++ b/internal/controllers/endpoint/actuator.go @@ -201,12 +201,10 @@ func (actuator endpointActuator) updateResource(ctx context.Context, obj orcObje _, 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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/floatingip/actuator.go b/internal/controllers/floatingip/actuator.go index 6d7eade89..e83148490 100644 --- a/internal/controllers/floatingip/actuator.go +++ b/internal/controllers/floatingip/actuator.go @@ -243,12 +243,10 @@ func (actuator floatingipActuator) CreateResource(ctx context.Context, obj *orcv osResource, err := actuator.osClient.CreateFloatingIP(ctx, &createOpts) - // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) - } - if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } return nil, progress.WrapError(err) } return osResource, nil @@ -284,10 +282,10 @@ func (actuator floatingipActuator) updateResource(ctx context.Context, obj orcOb _, err = actuator.osClient.UpdateFloatingIP(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/group/actuator.go b/internal/controllers/group/actuator.go index e3a75f817..87e84a3e1 100644 --- a/internal/controllers/group/actuator.go +++ b/internal/controllers/group/actuator.go @@ -188,12 +188,10 @@ func (actuator groupActuator) updateResource(ctx context.Context, obj orcObjectP _, err = actuator.osClient.UpdateGroup(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/image/actuator.go b/internal/controllers/image/actuator.go index fd40daaa8..b733bb3b7 100644 --- a/internal/controllers/image/actuator.go +++ b/internal/controllers/image/actuator.go @@ -171,9 +171,11 @@ func (actuator imageActuator) CreateResource(ctx context.Context, obj *orcv1alph Properties: additionalProperties, }) - // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating image: "+err.Error(), err) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating image: "+err.Error(), err) + } + return nil, progress.WrapError(err) } if err != nil { @@ -208,10 +210,10 @@ func (actuator imageActuator) UpdateResource(ctx context.Context, obj orcObjectP _, err := actuator.osClient.UpdateImage(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/network/actuator.go b/internal/controllers/network/actuator.go index 349609e7e..d6b9b273c 100644 --- a/internal/controllers/network/actuator.go +++ b/internal/controllers/network/actuator.go @@ -194,8 +194,8 @@ func (actuator networkActuator) CreateResource(ctx context.Context, obj orcObjec osResource, err := actuator.osClient.CreateNetwork(ctx, createOpts) if err != nil { - // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsConflict(err) { + // We should require the spec to be updated before retrying a create which returned a non-retryable error + if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) } return nil, progress.WrapError(err) @@ -251,10 +251,10 @@ func (actuator networkActuator) updateResource(ctx context.Context, obj orcObjec _, err = actuator.osClient.UpdateNetwork(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 0b525a903..6280c7822 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -328,8 +328,8 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha osResource, err := actuator.osClient.CreatePort(ctx, &portTrustedOpts) if err != nil { - // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsConflict(err) { + // We should require the spec to be updated before retrying a create which returned a non-retryable error + if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) } return nil, progress.WrapError(err) @@ -438,12 +438,10 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT _, err = actuator.osClient.UpdatePort(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/project/actuator.go b/internal/controllers/project/actuator.go index 3f01ba9bc..1ea580e8c 100644 --- a/internal/controllers/project/actuator.go +++ b/internal/controllers/project/actuator.go @@ -218,8 +218,11 @@ func (actuator projectActuator) updateResource(ctx context.Context, obj orcObjec _, err = actuator.osClient.UpdateProject(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + return progress.WrapError(err) } if err != nil { return progress.WrapError(err) diff --git a/internal/controllers/role/actuator.go b/internal/controllers/role/actuator.go index 3e4989ae0..5203a235b 100644 --- a/internal/controllers/role/actuator.go +++ b/internal/controllers/role/actuator.go @@ -182,12 +182,10 @@ func (actuator roleActuator) updateResource(ctx context.Context, obj orcObjectPT _, err = actuator.osClient.UpdateRole(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/router/actuator.go b/internal/controllers/router/actuator.go index 00e08d715..6369f4d9c 100644 --- a/internal/controllers/router/actuator.go +++ b/internal/controllers/router/actuator.go @@ -179,12 +179,10 @@ func (actuator routerActuator) CreateResource(ctx context.Context, obj *orcv1alp osResource, err := actuator.osClient.CreateRouter(ctx, &createOpts) - // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) - } - if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } return nil, progress.WrapError(err) } return osResource, nil @@ -222,10 +220,10 @@ func (actuator routerActuator) updateResource(ctx context.Context, obj orcObject _, err = actuator.osClient.UpdateRouter(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/securitygroup/actuator.go b/internal/controllers/securitygroup/actuator.go index f2bd38873..7830ab3ff 100644 --- a/internal/controllers/securitygroup/actuator.go +++ b/internal/controllers/securitygroup/actuator.go @@ -160,8 +160,8 @@ func (actuator securityGroupActuator) CreateResource(ctx context.Context, obj *o 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 - if orcerrors.IsConflict(err) { + // We should require the spec to be updated before retrying a create which returned a non-retryable error + if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) } return nil, progress.WrapError(err) @@ -209,10 +209,10 @@ func (actuator securityGroupActuator) updateResource(ctx context.Context, obj or _, err = actuator.osClient.UpdateSecGroup(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go index f86c50f82..873ef516d 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -454,10 +454,10 @@ func (actuator serverActuator) updateResource(ctx context.Context, obj orcObject _, err = actuator.osClient.UpdateServer(ctx, osResource.ID, updateOpts) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/service/actuator.go b/internal/controllers/service/actuator.go index 75e5be2e0..70688ae9d 100644 --- a/internal/controllers/service/actuator.go +++ b/internal/controllers/service/actuator.go @@ -156,12 +156,10 @@ func (actuator serviceActuator) updateResource(ctx context.Context, obj orcObjec _, err = actuator.osClient.UpdateService(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/subnet/actuator.go b/internal/controllers/subnet/actuator.go index 4a77bb710..8841a7133 100644 --- a/internal/controllers/subnet/actuator.go +++ b/internal/controllers/subnet/actuator.go @@ -247,12 +247,10 @@ func (actuator subnetActuator) CreateResource(ctx context.Context, obj orcObject osResource, err := actuator.osClient.CreateSubnet(ctx, &createOpts) - // We should require the spec to be updated before retrying a create which returned a conflict - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) - } - if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } return nil, progress.WrapError(err) } return osResource, nil @@ -312,12 +310,10 @@ func (actuator subnetActuator) updateResource(ctx context.Context, obj orcObject _, err = actuator.osClient.UpdateSubnet(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/trunk/actuator.go b/internal/controllers/trunk/actuator.go index ce9df2937..f79652694 100644 --- a/internal/controllers/trunk/actuator.go +++ b/internal/controllers/trunk/actuator.go @@ -255,12 +255,10 @@ func (actuator trunkActuator) updateResource(ctx context.Context, obj orcObjectP _, 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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } @@ -384,7 +382,7 @@ func (actuator trunkActuator) reconcileSubports(ctx context.Context, obj orcObje Subports: subportsToRemove, } if err := actuator.osClient.RemoveSubports(ctx, osResource.ID, removeOpts); err != nil { - if orcerrors.IsConflict(err) { + if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration removing subports: "+err.Error(), err) } return reconcileStatus.WithError(err) @@ -398,7 +396,7 @@ func (actuator trunkActuator) reconcileSubports(ctx context.Context, obj orcObje Subports: subportsToAdd, } if _, err := actuator.osClient.AddSubports(ctx, osResource.ID, addOpts); err != nil { - if orcerrors.IsConflict(err) { + if !orcerrors.IsRetryable(err) { err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration adding subports: "+err.Error(), err) } return reconcileStatus.WithError(err) diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index 1abc585f1..552eea60c 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -240,10 +240,10 @@ func (actuator userActuator) reconcilePassword(ctx context.Context, obj orcObjec Password: password, }) - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) - } if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } } @@ -295,12 +295,10 @@ func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT _, 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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/volume/actuator.go b/internal/controllers/volume/actuator.go index 17c21352b..3b086ccc3 100644 --- a/internal/controllers/volume/actuator.go +++ b/internal/controllers/volume/actuator.go @@ -243,12 +243,10 @@ func (actuator volumeActuator) updateResource(ctx context.Context, obj orcObject _, err = actuator.osClient.UpdateVolume(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/controllers/volumetype/actuator.go b/internal/controllers/volumetype/actuator.go index 8ba83f033..85c5ed503 100644 --- a/internal/controllers/volumetype/actuator.go +++ b/internal/controllers/volumetype/actuator.go @@ -202,12 +202,10 @@ func (actuator volumetypeActuator) updateResource(ctx context.Context, obj orcOb _, err = actuator.osClient.UpdateVolumeType(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } diff --git a/internal/util/errors/errors.go b/internal/util/errors/errors.go index eafb5ecae..98f7dc5b9 100644 --- a/internal/util/errors/errors.go +++ b/internal/util/errors/errors.go @@ -51,15 +51,26 @@ func (e noMatchesError) Is(err error) bool { return err == ErrFilterMatch } +// IsRetryable returns true if err may succeed when retried without changes to +// the spec. This includes HTTP error responses other than 409 Conflict, since +// some HTTP errors (like 400 Bad Request) can be transient when a dependency +// is not yet ready in OpenStack, and server errors (5xx) are typically +// transient. +// +// Non-HTTP errors from gophercloud (e.g. client-side validation such as +// banned value_spec keys), 409 Conflict, and 501 Not Implemented are not +// retryable. func IsRetryable(err error) bool { - var errUnexpectedResponseCode gophercloud.ErrUnexpectedResponseCode - if errors.As(err, &errUnexpectedResponseCode) { - statusCode := errUnexpectedResponseCode.GetStatusCode() - return statusCode >= 500 && statusCode != http.StatusNotImplemented + if IsConflict(err) || IsNotImplementedError(err) { + return false } - return false + + var errUnexpectedResponseCode gophercloud.ErrUnexpectedResponseCode + return errors.As(err, &errUnexpectedResponseCode) } +// IsNotFound returns true if err indicates the requested OpenStack resource +// was not found (HTTP 404 or gophercloud's ErrResourceNotFound). func IsNotFound(err error) bool { if err == nil { return false @@ -78,14 +89,17 @@ func IsNotFound(err error) bool { return gophercloud.ResponseCodeIs(err, http.StatusNotFound) } +// IsInvalidError returns true if err is an HTTP 400 Bad Request response. func IsInvalidError(err error) bool { return gophercloud.ResponseCodeIs(err, http.StatusBadRequest) } +// IsConflict returns true if err is an HTTP 409 Conflict response. func IsConflict(err error) bool { return gophercloud.ResponseCodeIs(err, http.StatusConflict) } +// IsNotImplementedError returns true if err is an HTTP 501 Not Implemented response. func IsNotImplementedError(err error) bool { return gophercloud.ResponseCodeIs(err, http.StatusNotImplemented) } From f29a2d43d9c869d8a8d38a5d528e1b9bcccf0cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 30 Apr 2026 15:26:35 +0200 Subject: [PATCH 179/237] errors: treat Neutron quota-exceeded 409 errors as retryable Neutron returns HTTP 409 Conflict for quota-exceeded errors with an "OverQuota" type in the response body. Unlike other 409 errors, quota can free up without changes to the resource spec, so these should be retried. Add an isNeutronQuotaError helper that inspects the response body of HTTP errors for the "OverQuota" string, and use it in IsRetryable to allow retries on quota-exceeded 409s. Also add unit tests for IsRetryable covering all error categories. Fixes #667 --- internal/util/errors/errors.go | 24 ++++++- internal/util/errors/errors_test.go | 108 ++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 internal/util/errors/errors_test.go diff --git a/internal/util/errors/errors.go b/internal/util/errors/errors.go index 98f7dc5b9..710dba16a 100644 --- a/internal/util/errors/errors.go +++ b/internal/util/errors/errors.go @@ -17,6 +17,7 @@ limitations under the License. package errors import ( + "bytes" "errors" "fmt" "net/http" @@ -59,9 +60,17 @@ func (e noMatchesError) Is(err error) bool { // // Non-HTTP errors from gophercloud (e.g. client-side validation such as // banned value_spec keys), 409 Conflict, and 501 Not Implemented are not -// retryable. +// retryable. The exception is Neutron quota-exceeded errors, which are +// returned as 409 but are retryable because quota can free up without spec +// changes. func IsRetryable(err error) bool { - if IsConflict(err) || IsNotImplementedError(err) { + if IsConflict(err) { + // Neutron returns 409 for quota-exceeded errors, but these are + // retryable because quota can free up without spec changes. + return isNeutronQuotaError(err) + } + + if IsNotImplementedError(err) { return false } @@ -69,6 +78,17 @@ func IsRetryable(err error) bool { return errors.As(err, &errUnexpectedResponseCode) } +// isNeutronQuotaError returns true if err is an HTTP error response whose +// body indicates a Neutron quota-exceeded condition. Neutron returns quota +// errors as 409 Conflict with an "OverQuota" type in the response body. +func isNeutronQuotaError(err error) bool { + var errUnexpectedResponseCode gophercloud.ErrUnexpectedResponseCode + if !errors.As(err, &errUnexpectedResponseCode) { + return false + } + return bytes.Contains(errUnexpectedResponseCode.Body, []byte("OverQuota")) +} + // IsNotFound returns true if err indicates the requested OpenStack resource // was not found (HTTP 404 or gophercloud's ErrResourceNotFound). func IsNotFound(err error) bool { diff --git a/internal/util/errors/errors_test.go b/internal/util/errors/errors_test.go new file mode 100644 index 000000000..da38c5fca --- /dev/null +++ b/internal/util/errors/errors_test.go @@ -0,0 +1,108 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 errors + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gophercloud/gophercloud/v2" +) + +func newHTTPError(statusCode int, body string) error { + return gophercloud.ErrUnexpectedResponseCode{ + Actual: statusCode, + Body: []byte(body), + } +} + +func TestIsRetryable(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + { + name: "nil error is not retryable", + err: nil, + want: false, + }, + { + name: "non-HTTP error is not retryable", + err: fmt.Errorf("some client-side validation error"), + want: false, + }, + { + name: "409 Conflict is not retryable", + err: newHTTPError(http.StatusConflict, `{"NeutronError": {"type": "IpAddressInUse"}}`), + want: false, + }, + { + name: "409 Conflict with Neutron OverQuota is retryable", + err: newHTTPError(http.StatusConflict, `{"NeutronError": {"type": "OverQuota", "message": "Quota exceeded for resources: port."}}`), + want: true, + }, + { + name: "501 Not Implemented is not retryable", + err: newHTTPError(http.StatusNotImplemented, ""), + want: false, + }, + { + name: "400 Bad Request is retryable", + err: newHTTPError(http.StatusBadRequest, `{"NeutronError": {"type": "BadRequest"}}`), + want: true, + }, + { + name: "403 Forbidden is retryable", + err: newHTTPError(http.StatusForbidden, ""), + want: true, + }, + { + name: "500 Internal Server Error is retryable", + err: newHTTPError(http.StatusInternalServerError, ""), + want: true, + }, + { + name: "503 Service Unavailable is retryable", + err: newHTTPError(http.StatusServiceUnavailable, ""), + want: true, + }, + { + name: "wrapped non-HTTP error is not retryable", + err: fmt.Errorf("wrapping: %w", fmt.Errorf("banned key")), + want: false, + }, + { + name: "wrapped 409 is not retryable", + err: fmt.Errorf("wrapping: %w", newHTTPError(http.StatusConflict, "")), + want: false, + }, + { + name: "wrapped 409 with OverQuota is retryable", + err: fmt.Errorf("wrapping: %w", newHTTPError(http.StatusConflict, `{"NeutronError": {"type": "OverQuota"}}`)), + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsRetryable(tt.err); got != tt.want { + t.Errorf("IsRetryable() = %v, want %v", got, tt.want) + } + }) + } +} From 4848dc15f65059fe1329b415b3e33f90dd75c718 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 18:21:51 +0000 Subject: [PATCH 180/237] :seedling:(deps): Bump github.com/onsi/ginkgo/v2 Bumps the all-go-mod-patch-and-minor group with 1 update in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo). Updates `github.com/onsi/ginkgo/v2` from 2.28.1 to 2.28.2 - [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.28.1...v2.28.2) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.28.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 | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 40006fec8..14b91641c 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-cmp v0.7.0 github.com/gophercloud/gophercloud/v2 v2.12.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/ginkgo/v2 v2.28.2 github.com/onsi/gomega v1.39.1 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 diff --git a/go.sum b/go.sum index 959a64dee..316d97628 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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= +github.com/onsi/ginkgo/v2 v2.28.2/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= From bca99894663b6d1271d1c985bab8637c61479778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 12 May 2026 07:50:25 +0200 Subject: [PATCH 181/237] build: bump Go to 1.25.10 Fixes GO-2026-4986, GO-2026-4982, GO-2026-4980, GO-2026-4977, GO-2026-4971 in net/mail, html/template, and net standard library packages. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 518e39318..00dd7499e 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.69.3 -GO_VERSION ?= 1.25.9 +GO_VERSION ?= 1.25.10 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) From 467915eb8bf1a7d7455861716055439ce29b8b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 12 May 2026 07:50:51 +0200 Subject: [PATCH 182/237] go.mod: bump golang.org/x/net to v0.53.0 Fixes GO-2026-4918: infinite loop in HTTP/2 transport when given bad SETTINGS_MAX_FRAME_SIZE. --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 14b91641c..090b9ba2e 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.34.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/term v0.41.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.43.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect diff --git a/go.sum b/go.sum index 316d97628..4488e99e1 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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= 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.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/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= From 0a7054a2526fc0bfe82e173946352e04f56e38ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 06:51:08 +0000 Subject: [PATCH 183/237] :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 1 update in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo). Updates `github.com/onsi/ginkgo/v2` from 2.28.2 to 2.28.3 - [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.28.2...v2.28.3) Updates `github.com/onsi/gomega` from 1.39.1 to 1.40.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.39.1...v1.40.0) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.28.3 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.40.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 | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 090b9ba2e..5023f8a3f 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/google/go-cmp v0.7.0 github.com/gophercloud/gophercloud/v2 v2.12.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.28.2 - github.com/onsi/gomega v1.39.1 + github.com/onsi/ginkgo/v2 v2.28.3 + github.com/onsi/gomega v1.40.0 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 golang.org/x/text v0.36.0 @@ -50,7 +50,7 @@ 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/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // 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.34.0 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/term v0.42.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/tools v0.44.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-20251202230838-ff82c1b0f217 // indirect diff --git a/go.sum b/go.sum index 4488e99e1..a0397552c 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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/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.12.0 h1:Gxmc/Bog1UDKkxTcQW7MSPTDviJXpLeEgVeN5KrxoCo= @@ -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.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= -github.com/onsi/ginkgo/v2 v2.28.2/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/onsi/ginkgo/v2 v2.28.3 h1:4JvMdwtFU0imd8fHx25OJXoDMRexnf8v5NHKYSTTji4= +github.com/onsi/ginkgo/v2 v2.28.3/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= +github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= 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,8 +207,8 @@ 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.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= 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= @@ -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.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= 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 1c81698486057271f3ad64647b1c341e9bd55b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 12 May 2026 10:40:56 +0200 Subject: [PATCH 184/237] Bump kuttl to v0.26.0 In particular, this fixes a possible namespace collision we've seem multiple times in CI. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 518e39318..5ead1ddec 100644 --- a/Makefile +++ b/Makefile @@ -316,7 +316,7 @@ 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 +KUTTL_VERSION ?= v0.26.0 GOVULNCHECK_VERSION ?= v1.1.4 OPERATOR_SDK_VERSION ?= v1.42.2 From d487b47426c7774889866344531f4a1c6b3d4f40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 23:58:33 +0000 Subject: [PATCH 185/237] :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: [golang.org/x/text](https://github.com/golang/text) and [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery). Updates `golang.org/x/text` from 0.36.0 to 0.37.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.36.0...v0.37.0) Updates `k8s.io/apimachinery` from 0.34.7 to 0.34.8 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.7...v0.34.8) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/apimachinery dependency-version: 0.34.8 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 | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5023f8a3f..7fec8d66a 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/onsi/gomega v1.40.0 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 - golang.org/x/text v0.36.0 + golang.org/x/text v0.37.0 k8s.io/api v0.34.7 - k8s.io/apimachinery v0.34.7 + k8s.io/apimachinery v0.34.8 k8s.io/client-go v0.34.7 k8s.io/code-generator v0.34.7 k8s.io/klog/v2 v2.130.1 diff --git a/go.sum b/go.sum index a0397552c..04d8022be 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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= @@ -275,8 +275,8 @@ k8s.io/api v0.34.7 h1:186BQYhLYaaU0jhC4/uC3s4cQ+YSk0BlazRnEldXiqY= k8s.io/api v0.34.7/go.mod h1:2qet0m0IfHSwXp+RLnunTWADUjy6qaXDa4PdeUnTJ9s= 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.7 h1:nkAtvxByvBFSyIyOTrFKcy6pgQ4G1NCKsypWArLdNSY= -k8s.io/apimachinery v0.34.7/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= +k8s.io/apimachinery v0.34.8 h1:Vjyd/TdakksL0g7ikxyaAk3s3qgqRRYz9vj0AudwNzc= +k8s.io/apimachinery v0.34.8/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= 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.7 h1:zxksYcHi4DMv6HfjMbHTfcV3jYZh7BY3yH18maoRxsA= From 634005a800cd581cb21928df52b1575a99789b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 23:59:28 +0000 Subject: [PATCH 186/237] :seedling:(deps): Bump actions/labeler in the all-github-actions group Bumps the all-github-actions group with 1 update: [actions/labeler](https://github.com/actions/labeler). Updates `actions/labeler` from 6.0.1 to 6.1.0 - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/634933edcd8ababfe52f92936142cc22ac488b1b...f27b608878404679385c85cfa523b85ccb86e213) --- updated-dependencies: - dependency-name: actions/labeler dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor 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 32383da6c..17f57643d 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -81,4 +81,4 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # tag=v6.0.1 + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # tag=v6.1.0 From 7b89945bd1df4fab28b12765935ccb6a93935d4a Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Thu, 2 Apr 2026 17:09:44 +0100 Subject: [PATCH 187/237] Initial Commit Used Scaffolding tool to create skeleton RoleAssignment Controller Signed-off-by: Daniel Lawton --- api/v1alpha1/roleassignment_types.go | 144 +++++++ .../openstack_v1alpha1_roleassignment.yaml | 14 + .../controllers/roleassignment/actuator.go | 359 ++++++++++++++++++ .../roleassignment/actuator_test.go | 119 ++++++ .../controllers/roleassignment/controller.go | 282 ++++++++++++++ internal/controllers/roleassignment/status.go | 68 ++++ .../roleassignment-create-full/00-assert.yaml | 53 +++ .../00-create-resource.yaml | 85 +++++ .../roleassignment-create-full/00-secret.yaml | 6 + .../roleassignment-create-full/README.md | 11 + .../00-assert.yaml | 32 ++ .../00-create-resource.yaml | 28 ++ .../00-secret.yaml | 6 + .../01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../roleassignment-create-minimal/README.md | 15 + .../roleassignment-dependency/00-assert.yaml | 90 +++++ .../00-create-resources-missing-deps.yaml | 98 +++++ .../roleassignment-dependency/00-secret.yaml | 6 + .../roleassignment-dependency/01-assert.yaml | 90 +++++ .../01-create-dependencies.yaml | 71 ++++ .../roleassignment-dependency/02-assert.yaml | 41 ++ .../02-delete-dependencies.yaml | 17 + .../roleassignment-dependency/03-assert.yaml | 17 + .../03-delete-resources.yaml | 22 ++ .../tests/roleassignment-dependency/README.md | 21 + .../00-assert.yaml | 25 ++ .../00-import-resource.yaml | 82 ++++ .../00-secret.yaml | 6 + .../01-assert.yaml | 40 ++ .../01-create-trap-resource.yaml | 84 ++++ .../02-assert.yaml | 54 +++ .../02-create-resource.yaml | 83 ++++ .../03-assert.yaml | 14 + .../03-delete-import-dependencies.yaml | 15 + .../04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../README.md | 29 ++ .../00-assert.yaml | 30 ++ .../00-create-resources.yaml | 43 +++ .../00-secret.yaml | 6 + .../01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../roleassignment-import-error/README.md | 13 + .../roleassignment-import/00-assert.yaml | 15 + .../00-import-resource.yaml | 15 + .../roleassignment-import/00-secret.yaml | 6 + .../roleassignment-import/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 31 ++ .../roleassignment-import/02-assert.yaml | 33 ++ .../02-create-resource.yaml | 28 ++ .../tests/roleassignment-import/README.md | 18 + .../roleassignment-update/00-assert.yaml | 26 ++ .../00-minimal-resource.yaml | 28 ++ .../roleassignment-update/00-secret.yaml | 6 + .../roleassignment-update/01-assert.yaml | 17 + .../01-updated-resource.yaml | 10 + .../roleassignment-update/02-assert.yaml | 26 ++ .../02-reverted-resource.yaml | 7 + .../tests/roleassignment-update/README.md | 17 + internal/osclients/roleassignment.go | 104 +++++ test/apivalidations/roleassignment_test.go | 176 +++++++++ 62 files changed, 2875 insertions(+) create mode 100644 api/v1alpha1/roleassignment_types.go create mode 100644 config/samples/openstack_v1alpha1_roleassignment.yaml create mode 100644 internal/controllers/roleassignment/actuator.go create mode 100644 internal/controllers/roleassignment/actuator_test.go create mode 100644 internal/controllers/roleassignment/controller.go create mode 100644 internal/controllers/roleassignment/status.go create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/README.md create mode 100644 internal/osclients/roleassignment.go create mode 100644 test/apivalidations/roleassignment_test.go diff --git a/api/v1alpha1/roleassignment_types.go b/api/v1alpha1/roleassignment_types.go new file mode 100644 index 000000000..36e82ff05 --- /dev/null +++ b/api/v1alpha1/roleassignment_types.go @@ -0,0 +1,144 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +// RoleAssignmentResourceSpec contains the desired state of the resource. +type RoleAssignmentResourceSpec 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"` + + // roleRef is a reference to the ORC Role which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="roleRef is immutable" + RoleRef KubernetesNameRef `json:"roleRef,omitempty"` + + // userRef is a reference to the ORC User which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable" + UserRef *KubernetesNameRef `json:"userRef,omitempty"` + + // groupRef is a reference to the ORC Group which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="groupRef is immutable" + GroupRef *KubernetesNameRef `json:"groupRef,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"` + + // 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"` + + // 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/roles + // + // 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"` +} + +// RoleAssignmentFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type RoleAssignmentFilter 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"` + + // roleRef is a reference to the ORC Role which this resource is associated with. + // +optional + RoleRef *KubernetesNameRef `json:"roleRef,omitempty"` + + // userRef is a reference to the ORC User which this resource is associated with. + // +optional + UserRef *KubernetesNameRef `json:"userRef,omitempty"` + + // groupRef is a reference to the ORC Group which this resource is associated with. + // +optional + GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,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/roles +} + +// RoleAssignmentResourceStatus represents the observed state of the resource. +type RoleAssignmentResourceStatus 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"` + + // roleID is the ID of the Role to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + RoleID string `json:"roleID,omitempty"` + + // userID is the ID of the User to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + UserID string `json:"userID,omitempty"` + + // groupID is the ID of the Group to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + GroupID string `json:"groupID,omitempty"` + + // projectID is the ID of the Project to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // domainID is the ID of the Domain to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + DomainID string `json:"domainID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the RoleAssignment structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles +} diff --git a/config/samples/openstack_v1alpha1_roleassignment.yaml b/config/samples/openstack_v1alpha1_roleassignment.yaml new file mode 100644 index 000000000..f1294f7ce --- /dev/null +++ b/config/samples/openstack_v1alpha1_roleassignment.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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 RoleAssignment + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go new file mode 100644 index 000000000..32fafce63 --- /dev/null +++ b/internal/controllers/roleassignment/actuator.go @@ -0,0 +1,359 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 roleassignment + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + 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 = roles.RoleAssignment + + 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 roleassignmentActuator struct { + osClient osclients.RoleAssignmentClient + k8sClient client.Client +} + +var _ createResourceActuator = roleassignmentActuator{} +var _ deleteResourceActuator = roleassignmentActuator{} + +func (roleassignmentActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator roleassignmentActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetRoleAssignment(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator roleassignmentActuator) 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 := roles.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListRoleAssignments(ctx, listOpts), true +} + +func (actuator roleassignmentActuator) 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 + + role, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.RoleRef, "Role", + func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + user, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.UserRef, "User", + func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + group, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.GroupRef, "Group", + func(dep *orcv1alpha1.Group) 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) + + 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 := roles.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + RoleID: ptr.Deref(role.Status.ID, ""), + UserID: ptr.Deref(user.Status.ID, ""), + GroupID: ptr.Deref(group.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + DomainID: ptr.Deref(domain.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListRoleAssignments(ctx, listOpts), reconcileStatus +} + +func (actuator roleassignmentActuator) 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 roleID string + role, roleDepRS := roleDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(roleDepRS) + if role != nil { + roleID = ptr.Deref(role.Status.ID, "") + } + + var userID string + if resource.UserRef != nil { + user, userDepRS := userDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS) + if user != nil { + userID = ptr.Deref(user.Status.ID, "") + } + } + + var groupID string + if resource.GroupRef != nil { + group, groupDepRS := groupDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Group) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(groupDepRS) + if group != nil { + groupID = ptr.Deref(group.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, "") + } + } + + 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, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := roles.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + RoleID: roleID, + UserID: userID, + GroupID: groupID, + ProjectID: projectID, + DomainID: domainID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateRoleAssignment(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 roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteRoleAssignment(ctx, resource.ID)) +} + +func (actuator roleassignmentActuator) 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 := roles.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.UpdateRoleAssignment(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 roles.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToRoleAssignmentUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["role_assignment"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *roles.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *roles.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator roleassignmentActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type roleassignmentHelperFactory struct{} + +var _ helperFactory = roleassignmentHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.RoleAssignment, controller interfaces.ResourceController) (roleassignmentActuator, 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 roleassignmentActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return roleassignmentActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewRoleAssignmentClient() + if err != nil { + return roleassignmentActuator{}, progress.WrapError(err) + } + + return roleassignmentActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (roleassignmentHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return roleassignmentAdapter{obj} +} + +func (roleassignmentHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (roleassignmentHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go new file mode 100644 index 000000000..0433ecf35 --- /dev/null +++ b/internal/controllers/roleassignment/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 roleassignment + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + 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 roles.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: roles.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: roles.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.RoleAssignment{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := roles.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.RoleAssignmentResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := roles.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/roleassignment/controller.go b/internal/controllers/roleassignment/controller.go new file mode 100644 index 000000000..631ea1236 --- /dev/null +++ b/internal/controllers/roleassignment/controller.go @@ -0,0 +1,282 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 roleassignment + +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 = "roleassignment" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roleassignments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roleassignments/status,verbs=get;update;patch + +type roleassignmentReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return roleassignmentReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (roleassignmentReconcilerConstructor) GetName() string { + return controllerName +} + +var roleDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Role]( + "spec.resource.roleRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.RoleRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var userDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.User]( + "spec.resource.userRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.UserRef == nil { + return nil + } + return []string{string(*resource.UserRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var groupDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Group]( + "spec.resource.groupRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.GroupRef == nil { + return nil + } + return []string{string(*resource.GroupRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Domain]( + "spec.resource.domainRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.DomainRef == nil { + return nil + } + return []string{string(*resource.DomainRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var roleImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Role]( + "spec.import.filter.roleRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.RoleRef == nil { + return nil + } + return []string{string(*resource.Filter.RoleRef)} + }, +) + +var userImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.User]( + "spec.import.filter.userRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.UserRef == nil { + return nil + } + return []string{string(*resource.Filter.UserRef)} + }, +) + +var groupImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Group]( + "spec.import.filter.groupRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.GroupRef == nil { + return nil + } + return []string{string(*resource.Filter.GroupRef)} + }, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +var domainImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Domain]( + "spec.import.filter.domainRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.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 roleassignmentReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + roleWatchEventHandler, err := roleDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + userWatchEventHandler, err := userDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + groupWatchEventHandler, err := groupDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainWatchEventHandler, err := domainDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + roleImportWatchEventHandler, err := roleImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + userImportWatchEventHandler, err := userImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + groupImportWatchEventHandler, err := groupImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.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.Role{}, roleWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Role{})), + ). + Watches(&orcv1alpha1.User{}, userWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.User{})), + ). + Watches(&orcv1alpha1.Group{}, groupWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Group{})), + ). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Role{}, roleImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Role{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.User{}, userImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.User{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Group{}, groupImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Group{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + For(&orcv1alpha1.RoleAssignment{}) + + if err := errors.Join( + roleDependency.AddToManager(ctx, mgr), + userDependency.AddToManager(ctx, mgr), + groupDependency.AddToManager(ctx, mgr), + projectDependency.AddToManager(ctx, mgr), + domainDependency.AddToManager(ctx, mgr), + roleImportDependency.AddToManager(ctx, mgr), + userImportDependency.AddToManager(ctx, mgr), + groupImportDependency.AddToManager(ctx, mgr), + projectImportDependency.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, roleassignmentHelperFactory{}, roleassignmentStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/roleassignment/status.go b/internal/controllers/roleassignment/status.go new file mode 100644 index 000000000..a3a720369 --- /dev/null +++ b/internal/controllers/roleassignment/status.go @@ -0,0 +1,68 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 roleassignment + +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 roleassignmentStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.RoleAssignmentApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.RoleAssignmentStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.RoleAssignment, *osResourceT, *objectApplyT, *statusApplyT] = roleassignmentStatusWriter{} + +func (roleassignmentStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.RoleAssignment(name, namespace) +} + +func (roleassignmentStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.RoleAssignment, 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 (roleassignmentStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus(). + WithRoleID(osResource.RoleID). + WithUserID(osResource.UserID). + WithGroupID(osResource.GroupID). + WithProjectID(osResource.ProjectID). + WithDomainID(osResource.DomainID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the RoleAssignmentResourceStatus 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/roleassignment/tests/roleassignment-create-full/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml new file mode 100644 index 000000000..7da99c8d0 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml @@ -0,0 +1,53 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-full +status: + resource: + name: roleassignment-create-full-override + description: RoleAssignment 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: RoleAssignment + name: roleassignment-create-full + ref: roleassignment + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-create-full + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-create-full + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: roleassignment-create-full + ref: group + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-create-full + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-create-full + ref: domain +assertAll: + - celExpr: "roleassignment.status.id != ''" + - celExpr: "roleassignment.status.resource.roleID == role.status.id" + - celExpr: "roleassignment.status.resource.userID == user.status.id" + - celExpr: "roleassignment.status.resource.groupID == group.status.id" + - celExpr: "roleassignment.status.resource.projectID == project.status.id" + - celExpr: "roleassignment.status.resource.domainID == domain.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml new file mode 100644 index 000000000..ce3174c84 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: roleassignment-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: Group +metadata: + name: roleassignment-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: roleassignment-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: Domain +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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: roleassignment-create-full-override + description: RoleAssignment from "create full" test + roleRef: roleassignment-create-full + userRef: roleassignment-create-full + groupRef: roleassignment-create-full + projectRef: roleassignment-create-full + domainRef: roleassignment-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-create-full/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md new file mode 100644 index 000000000..74756b176 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md @@ -0,0 +1,11 @@ +# Create a RoleAssignment with all the options + +## Step 00 + +Create a RoleAssignment 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/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml new file mode 100644 index 000000000..4106e13f4 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-minimal +status: + resource: + name: roleassignment-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: RoleAssignment + name: roleassignment-create-minimal + ref: roleassignment + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-create-minimal + ref: role +assertAll: + - celExpr: "roleassignment.status.id != ''" + - celExpr: "roleassignment.status.resource.roleID == role.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..28bf9a7aa --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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: + roleRef: roleassignment-create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml new file mode 100644 index 000000000..c4d367d4a --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment' in secret.metadata.finalizers" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-create-minimal/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md new file mode 100644 index 000000000..08f0e40c6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a RoleAssignment with the minimum options + +## Step 00 + +Create a minimal RoleAssignment, 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/roleassignment/tests/roleassignment-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml new file mode 100644 index 000000000..ce8bc6357 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-role +status: + conditions: + - type: Available + message: Waiting for Role/roleassignment-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Role/roleassignment-dependency-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-user +status: + conditions: + - type: Available + message: Waiting for User/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for User/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-group +status: + conditions: + - type: Available + message: Waiting for Group/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Group/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-domain +status: + conditions: + - type: Available + message: Waiting for Domain/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Domain/roleassignment-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..9666ee6c6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-dependency-no-role +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-user +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency + userRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-group +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency + groupRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: + roleRef: roleassignment-dependency + projectRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: + roleRef: roleassignment-dependency + domainRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: roleassignment-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + roleRef: roleassignment-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml new file mode 100644 index 000000000..2fd8a79b1 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-dependency-no-role +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: RoleAssignment +metadata: + name: roleassignment-dependency-no-user +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: RoleAssignment +metadata: + name: roleassignment-dependency-no-group +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: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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 diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..5e18664c5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic roleassignment-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-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: Group +metadata: + name: roleassignment-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: roleassignment-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: Domain +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml new file mode 100644 index 000000000..d91675d9d --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-dependency + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-dependency + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: roleassignment-dependency + ref: group + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: roleassignment-dependency + ref: secret +assertAll: + - celExpr: "role.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in role.metadata.finalizers" + - celExpr: "user.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in user.metadata.finalizers" + - celExpr: "group.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in group.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in project.metadata.finalizers" + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in secret.metadata.finalizers" diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..7d2647c44 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete role.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete user.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete group.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete secret roleassignment-dependency --wait=false + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml new file mode 100644 index 000000000..c9d8176db --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..bd5ab1aec --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-role +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-user +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-group +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-project +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md new file mode 100644 index 000000000..2682e773e --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create RoleAssignments referencing non-existing resources. Each RoleAssignment is dependent on other non-existing resource. Verify that the RoleAssignments are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the RoleAssignments 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 RoleAssignments and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml new file mode 100644 index 000000000..d67606898 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..cbde79d20 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml @@ -0,0 +1,82 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + roleRef: roleassignment-import-dependency + userRef: roleassignment-import-dependency + groupRef: roleassignment-import-dependency + projectRef: roleassignment-import-dependency + domainRef: roleassignment-import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml new file mode 100644 index 000000000..bf4fc3938 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..d7edf5088 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,84 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-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 `roleassignment-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: + roleRef: roleassignment-import-dependency-not-this-one + userRef: roleassignment-import-dependency-not-this-one + groupRef: roleassignment-import-dependency-not-this-one + projectRef: roleassignment-import-dependency-not-this-one + domainRef: roleassignment-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml new file mode 100644 index 000000000..3c81e999a --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dependency + ref: roleassignment1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dependency-not-this-one + ref: roleassignment2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-import-dependency + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-import-dependency + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: roleassignment-import-dependency + ref: group + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-import-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-import-dependency + ref: domain +assertAll: + - celExpr: "roleassignment1.status.id != roleassignment2.status.id" + - celExpr: "roleassignment1.status.resource.roleID == role.status.id" + - celExpr: "roleassignment1.status.resource.userID == user.status.id" + - celExpr: "roleassignment1.status.resource.groupID == group.status.id" + - celExpr: "roleassignment1.status.resource.projectID == project.status.id" + - celExpr: "roleassignment1.status.resource.domainID == domain.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..aa6a4b273 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml @@ -0,0 +1,83 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: roleassignment-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: Group +metadata: + name: roleassignment-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: Project +metadata: + name: roleassignment-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: Domain +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-dependency-external + userRef: roleassignment-import-dependency-external + groupRef: roleassignment-import-dependency-external + projectRef: roleassignment-import-dependency-external + domainRef: roleassignment-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml new file mode 100644 index 000000000..5ae60674d --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..6779311b6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete role.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete user.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete group.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml new file mode 100644 index 000000000..1b9333e8c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get roleassignment.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..65a259c22 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md new file mode 100644 index 000000000..e42814f39 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported RoleAssignment + +## Step 00 + +Import a RoleAssignment that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the RoleAssignment is waiting for the dependency to be ready. + +## Step 01 + +Create a RoleAssignment matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a RoleAssignment matching the import filters. + +Verify that the observed status on the imported RoleAssignment corresponds to the spec of the created RoleAssignment. + +## 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 RoleAssignment and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml new file mode 100644 index 000000000..a0fbff00c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml new file mode 100644 index 000000000..a85f3b243 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment from "import error" test + roleRef: roleassignment-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment from "import error" test + roleRef: roleassignment-import-error + # TODO(scaffolding): add any required field diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-import-error/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml new file mode 100644 index 000000000..1f7e3a893 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml new file mode 100644 index 000000000..5589b99b8 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: RoleAssignment from "import error" test diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md new file mode 100644 index 000000000..26ca68ce4 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md @@ -0,0 +1,13 @@ +# Import RoleAssignment with more than one matching resources + +## Step 00 + +Create two RoleAssignments with identical specs. + +## Step 01 + +Ensure that an imported RoleAssignment with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml new file mode 100644 index 000000000..fdc36fdd0 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml new file mode 100644 index 000000000..e4d6ce579 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-external + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-import/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml new file mode 100644 index 000000000..9a908403b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: roleassignment-import-external-not-this-one + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..a991d3d80 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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 `roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment roleassignment-import-external from "roleassignment-import" test + roleRef: roleassignment-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml new file mode 100644 index 000000000..b5b5eb744 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-external + ref: roleassignment1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-external-not-this-one + ref: roleassignment2 +assertAll: + - celExpr: "roleassignment1.status.id != roleassignment2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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: roleassignment-import-external + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml new file mode 100644 index 000000000..a4613b0a5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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: RoleAssignment roleassignment-import-external from "roleassignment-import" test + roleRef: roleassignment-import + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/README.md b/internal/controllers/roleassignment/tests/roleassignment-import/README.md new file mode 100644 index 000000000..0d655cbb5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/README.md @@ -0,0 +1,18 @@ +# Import RoleAssignment + +## Step 00 + +Import a roleassignment that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a roleassignment 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 roleassignment matching the filter and verify that the observed status on the imported roleassignment corresponds to the spec of the created roleassignment. +Also, confirm that it does not adopt any roleassignment whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml new file mode 100644 index 000000000..09171f4ed --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-update + ref: roleassignment +assertAll: + - celExpr: "!has(roleassignment.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +status: + resource: + name: roleassignment-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/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml new file mode 100644 index 000000000..dded7b069 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-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: RoleAssignment +metadata: + name: roleassignment-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: + roleRef: roleassignment-update diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-update/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml new file mode 100644 index 000000000..6ac85e51b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +status: + resource: + name: roleassignment-update-updated + description: roleassignment-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/roleassignment/tests/roleassignment-update/01-updated-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml new file mode 100644 index 000000000..d07ade78c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +spec: + resource: + name: roleassignment-update-updated + description: roleassignment-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml new file mode 100644 index 000000000..15536b2d3 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-update + ref: roleassignment +assertAll: + - celExpr: "!has(roleassignment.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +status: + resource: + name: roleassignment-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/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-update/README.md b/internal/controllers/roleassignment/tests/roleassignment-update/README.md new file mode 100644 index 000000000..8c0d2dee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/README.md @@ -0,0 +1,17 @@ +# Update RoleAssignment + +## Step 00 + +Create a RoleAssignment 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/roleassignment.go b/internal/osclients/roleassignment.go new file mode 100644 index 000000000..62da755cd --- /dev/null +++ b/internal/osclients/roleassignment.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/roles" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type RoleAssignmentClient interface { + ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] + CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) + DeleteRoleAssignment(ctx context.Context, resourceID string) error + GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) + UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) +} + +type roleassignmentClient struct{ client *gophercloud.ServiceClient } + +// NewRoleAssignmentClient returns a new OpenStack client. +func NewRoleAssignmentClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (RoleAssignmentClient, 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 roleassignment service client: %v", err) + } + + return &roleassignmentClient{client}, nil +} + +func (c roleassignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { + pager := roles.List(c.client, listOpts) + return func(yield func(*roles.RoleAssignment, error) bool) { + _ = pager.EachPage(ctx, yieldPage(roles.ExtractRoleAssignments, yield)) + } +} + +func (c roleassignmentClient) CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { + return roles.Create(ctx, c.client, opts).Extract() +} + +func (c roleassignmentClient) DeleteRoleAssignment(ctx context.Context, resourceID string) error { + return roles.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c roleassignmentClient) GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) { + return roles.Get(ctx, c.client, resourceID).Extract() +} + +func (c roleassignmentClient) UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { + return roles.Update(ctx, c.client, id, opts).Extract() +} + +type roleassignmentErrorClient struct{ error } + +// NewRoleAssignmentErrorClient returns a RoleAssignmentClient in which every method returns the given error. +func NewRoleAssignmentErrorClient(e error) RoleAssignmentClient { + return roleassignmentErrorClient{e} +} + +func (e roleassignmentErrorClient) ListRoleAssignments(_ context.Context, _ roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { + return func(yield func(*roles.RoleAssignment, error) bool) { + yield(nil, e.error) + } +} + +func (e roleassignmentErrorClient) CreateRoleAssignment(_ context.Context, _ roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { + return nil, e.error +} + +func (e roleassignmentErrorClient) DeleteRoleAssignment(_ context.Context, _ string) error { + return e.error +} + +func (e roleassignmentErrorClient) GetRoleAssignment(_ context.Context, _ string) (*roles.RoleAssignment, error) { + return nil, e.error +} + +func (e roleassignmentErrorClient) UpdateRoleAssignment(_ context.Context, _ string, _ roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { + return nil, e.error +} diff --git a/test/apivalidations/roleassignment_test.go b/test/apivalidations/roleassignment_test.go new file mode 100644 index 000000000..64efff717 --- /dev/null +++ b/test/apivalidations/roleassignment_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 ( + roleassignmentName = "roleassignment" + roleassignmentID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func roleassignmentStub(namespace *corev1.Namespace) *orcv1alpha1.RoleAssignment { + obj := &orcv1alpha1.RoleAssignment{} + obj.Name = roleassignmentName + obj.Namespace = namespace.Name + return obj +} + +func testRoleAssignmentResource() *applyconfigv1alpha1.RoleAssignmentResourceSpecApplyConfiguration { + return applyconfigv1alpha1.RoleAssignmentResourceSpec(). + WithRoleRef("role") +} + +func baseRoleAssignmentPatch(obj client.Object) *applyconfigv1alpha1.RoleAssignmentApplyConfiguration { + return applyconfigv1alpha1.RoleAssignment(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.RoleAssignmentSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testRoleAssignmentImport() *applyconfigv1alpha1.RoleAssignmentImportApplyConfiguration { + return applyconfigv1alpha1.RoleAssignmentImport().WithID(roleassignmentID) +} + +var _ = Describe("ORC RoleAssignment API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.RoleAssignmentApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return roleassignmentStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.RoleAssignmentApplyConfiguration { + return baseRoleAssignmentPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithResource(testRoleAssignmentResource()) + }, + applyImport: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(testRoleAssignmentImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.RoleAssignment).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.RoleAssignment).Spec.ManagedOptions.OnDelete + }, + }) + + It("should reject a roleassignment without required fields", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(applyconfigv1alpha1.RoleAssignmentResourceSpec()) + Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) + }) + + It("should have immutable roleRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithRoleRef("role-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithRoleRef("role-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("roleRef is immutable"))) + }) + + It("should have immutable userRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithUserRef("user-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithUserRef("user-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("userRef is immutable"))) + }) + + It("should have immutable groupRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithGroupRef("group-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithGroupRef("group-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("groupRef is immutable"))) + }) + + It("should have immutable projectRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithProjectRef("project-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithProjectRef("project-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should have immutable domainRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) + + // TODO(scaffolding): Add more resource-specific validation tests. + // Some common things to test: + // - Immutability of fields with `self == oldSelf` validation + // - Enum validation (valid and invalid values) + // - Numeric range validation (min/max bounds) + // - Tag uniqueness (if the resource has tags with listType=set) + // - Format validation (CIDR, UUID, etc.) + // - Cross-field validation rules +}) From 856ad6ae3821c97b83d8006df5c691e98becbc30 Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Fri, 17 Apr 2026 15:46:06 +0100 Subject: [PATCH 188/237] Implement RoleAssignment controller treating role assignments as relationships rather than resources, since OpenStack doesn't assign IDs to role assignments. Key changes: - Custom reconciler: Ignores generic framework since role assignments lack OpenStack resource IDs - Component-based identification: Uses tuple (roleID, userID/groupID, projectID/domainID) stored in Status.Resource instead of UIDs - Status.ID intentionally nil: Components serve as natural identifiers - Immutable spec: Role assignments can't be modified after creation (matching Kubernetes RBAC behavior) - Deletion guards: All dependencies (Role, User/Group, Project/Domain) protected from deletion while in use E2E tests cover four actor-scope combinations: - roleassignment-create-user-project - roleassignment-create-user-domain - roleassignment-create-group-project - roleassignment-create-group-domain Plus roleassignment-dependency test verifying deletion guard behavior. Implementation details: - reconciler.go: Custom reconcile loop handling create/delete lifecycle - actuator.go: GetResourceByComponents() replaces GetOSResourceByID() Signed-off-by: Daniel Lawton --- PROJECT | 8 + api/v1alpha1/roleassignment_types.go | 100 ++-- api/v1alpha1/zz_generated.deepcopy.go | 237 +++++++++ .../zz_generated.roleassignment-resource.go | 179 +++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 383 +++++++++++++++ cmd/resource-generator/main.go | 4 + ...openstack.k-orc.cloud_roleassignments.yaml | 348 +++++++++++++ config/crd/kustomization.yaml | 1 + config/rbac/role.yaml | 2 + config/samples/kustomization.yaml | 1 + .../openstack_v1alpha1_roleassignment.yaml | 43 +- .../controllers/roleassignment/actuator.go | 464 ++++++++++-------- .../roleassignment/actuator_test.go | 103 +--- .../controllers/roleassignment/controller.go | 9 +- .../controllers/roleassignment/reconciler.go | 386 +++++++++++++++ internal/controllers/roleassignment/status.go | 48 +- .../roleassignment-create-full/00-assert.yaml | 53 -- .../00-create-resource.yaml | 85 ---- .../roleassignment-create-full/README.md | 11 - .../00-assert.yaml | 75 +++ .../00-create-resource.yaml | 54 ++ .../00-secret.yaml | 1 - .../01-assert.yaml | 9 + .../01-disable-domain.yaml | 7 + .../02-assert.yaml | 47 ++ .../02-delete-roleassignment.yaml | 7 + .../README.md | 15 + .../00-assert.yaml | 75 +++ .../00-create-resource.yaml | 54 ++ .../00-secret.yaml | 1 - .../01-assert.yaml | 47 ++ .../01-delete-roleassignment.yaml | 7 + .../README.md | 15 + .../00-assert.yaml | 32 -- .../00-create-resource.yaml | 28 -- .../01-assert.yaml | 11 - .../01-delete-secret.yaml | 7 - .../roleassignment-create-minimal/README.md | 15 - .../00-assert.yaml | 75 +++ .../00-create-resource.yaml | 54 ++ .../00-secret.yaml | 1 - .../01-assert.yaml | 9 + .../01-disable-domain.yaml | 7 + .../02-assert.yaml | 47 ++ .../02-delete-roleassignment.yaml} | 2 +- .../README.md | 15 + .../00-assert.yaml | 75 +++ .../00-create-resource.yaml | 54 ++ .../00-secret.yaml | 1 - .../01-assert.yaml | 47 ++ .../01-delete-roleassignment.yaml | 7 + .../README.md | 15 + .../roleassignment-dependency/00-assert.yaml | 91 +--- .../00-create-resources-missing-deps.yaml | 95 +--- .../roleassignment-dependency/00-secret.yaml | 1 - .../roleassignment-dependency/01-assert.yaml | 122 ++--- .../01-create-dependencies.yaml | 60 +-- .../roleassignment-dependency/02-assert.yaml | 68 ++- .../02-delete-dependencies.yaml | 15 +- .../roleassignment-dependency/03-assert.yaml | 15 +- .../03-delete-resources.yaml | 22 +- .../tests/roleassignment-dependency/README.md | 14 +- .../00-assert.yaml | 25 - .../00-import-resource.yaml | 82 ---- .../01-assert.yaml | 40 -- .../01-create-trap-resource.yaml | 84 ---- .../02-assert.yaml | 54 -- .../02-create-resource.yaml | 83 ---- .../03-assert.yaml | 14 - .../03-delete-import-dependencies.yaml | 15 - .../04-assert.yaml | 6 - .../README.md | 29 -- .../00-assert.yaml | 30 -- .../00-create-resources.yaml | 43 -- .../01-assert.yaml | 15 - .../01-import-resource.yaml | 13 - .../roleassignment-import-error/README.md | 13 - .../roleassignment-import/00-assert.yaml | 15 - .../00-import-resource.yaml | 15 - .../roleassignment-import/00-secret.yaml | 6 - .../roleassignment-import/01-assert.yaml | 34 -- .../01-create-trap-resource.yaml | 31 -- .../roleassignment-import/02-assert.yaml | 33 -- .../02-create-resource.yaml | 28 -- .../tests/roleassignment-import/README.md | 18 - .../roleassignment-update/00-assert.yaml | 26 - .../00-minimal-resource.yaml | 28 -- .../roleassignment-update/00-secret.yaml | 6 - .../roleassignment-update/01-assert.yaml | 17 - .../01-updated-resource.yaml | 10 - .../roleassignment-update/02-assert.yaml | 26 - .../02-reverted-resource.yaml | 7 - .../tests/roleassignment-update/README.md | 17 - .../roleassignment/zz_generated.adapter.go | 78 +++ .../roleassignment/zz_generated.controller.go | 45 ++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/roleassignment.go | 100 ++++ internal/osclients/roleassignment.go | 48 +- internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/roleassignment.go | 281 +++++++++++ .../api/v1alpha1/roleassignmentfilter.go | 79 +++ .../api/v1alpha1/roleassignmentimport.go | 48 ++ .../v1alpha1/roleassignmentresourcespec.go | 79 +++ .../v1alpha1/roleassignmentresourcestatus.go | 75 +++ .../api/v1alpha1/roleassignmentspec.go | 79 +++ .../api/v1alpha1/roleassignmentstatus.go | 66 +++ .../applyconfiguration/internal/internal.go | 120 +++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../api/v1alpha1/fake/fake_roleassignment.go | 53 ++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../typed/api/v1alpha1/roleassignment.go | 74 +++ .../api/v1alpha1/interface.go | 7 + .../api/v1alpha1/roleassignment.go | 102 ++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + .../listers/api/v1alpha1/roleassignment.go | 70 +++ test/apivalidations/roleassignment_test.go | 71 +-- website/docs/crd-reference.md | 149 ++++++ 124 files changed, 4444 insertions(+), 1980 deletions(-) create mode 100644 api/v1alpha1/zz_generated.roleassignment-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml create mode 100644 internal/controllers/roleassignment/reconciler.go delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-create-resource.yaml rename internal/controllers/roleassignment/tests/{roleassignment-create-minimal => roleassignment-create-group-domain}/00-secret.yaml (98%) create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-disable-domain.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-delete-roleassignment.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-domain/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-create-resource.yaml rename internal/controllers/roleassignment/tests/{roleassignment-import-dependency => roleassignment-create-group-project}/00-secret.yaml (98%) create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-delete-roleassignment.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-group-project/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-create-resource.yaml rename internal/controllers/roleassignment/tests/{roleassignment-create-full => roleassignment-create-user-domain}/00-secret.yaml (98%) create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-disable-domain.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-assert.yaml rename internal/controllers/roleassignment/tests/{roleassignment-import-dependency/04-delete-resource.yaml => roleassignment-create-user-domain/02-delete-roleassignment.yaml} (74%) create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-domain/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-create-resource.yaml rename internal/controllers/roleassignment/tests/{roleassignment-import-error => roleassignment-create-user-project}/00-secret.yaml (98%) create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-delete-roleassignment.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-user-project/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/README.md create mode 100644 internal/controllers/roleassignment/zz_generated.adapter.go create mode 100644 internal/controllers/roleassignment/zz_generated.controller.go create mode 100644 internal/osclients/mock/roleassignment.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go create mode 100644 pkg/clients/listers/api/v1alpha1/roleassignment.go diff --git a/PROJECT b/PROJECT index 73daa42d0..cd63db01a 100644 --- a/PROJECT +++ b/PROJECT @@ -112,6 +112,14 @@ resources: kind: Role path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: RoleAssignment + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/roleassignment_types.go b/api/v1alpha1/roleassignment_types.go index 36e82ff05..b2a947975 100644 --- a/api/v1alpha1/roleassignment_types.go +++ b/api/v1alpha1/roleassignment_types.go @@ -16,129 +16,89 @@ limitations under the License. package v1alpha1 -// RoleAssignmentResourceSpec contains the desired state of the resource. +// RoleAssignmentResourceSpec defines the desired role assignment. +// A role assignment grants a role to a user or group on a project or domain. +// Role assignments are immutable once created and identified by the combination +// of (role, actor, scope) rather than a separate ID. +// +kubebuilder:validation:XValidation:rule="(has(self.userRef) && !has(self.groupRef)) || (!has(self.userRef) && has(self.groupRef))",message="exactly one of userRef or groupRef is required" +// +kubebuilder:validation:XValidation:rule="(has(self.projectRef) && !has(self.domainRef)) || (!has(self.projectRef) && has(self.domainRef))",message="exactly one of projectRef or domainRef is required" +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="RoleAssignmentResourceSpec is immutable" type RoleAssignmentResourceSpec 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"` - - // roleRef is a reference to the ORC Role which this resource is associated with. + // roleRef references the Role being assigned. // +required - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="roleRef is immutable" RoleRef KubernetesNameRef `json:"roleRef,omitempty"` - // userRef is a reference to the ORC User which this resource is associated with. + // userRef references the User receiving the role assignment. + // Exactly one of userRef or groupRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable" UserRef *KubernetesNameRef `json:"userRef,omitempty"` - // groupRef is a reference to the ORC Group which this resource is associated with. + // groupRef references the Group receiving the role assignment. + // Exactly one of userRef or groupRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="groupRef is immutable" GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` - // projectRef is a reference to the ORC Project which this resource is associated with. + // projectRef references the Project scope for the assignment. + // Exactly one of projectRef or domainRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // domainRef is a reference to the ORC Domain which this resource is associated with. + // domainRef references the Domain scope for the assignment. + // Exactly one of projectRef or domainRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" DomainRef *KubernetesNameRef `json:"domainRef,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/roles - // - // 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"` } -// RoleAssignmentFilter defines an existing resource by its properties +// RoleAssignmentFilter defines import filter criteria for existing role assignments. // +kubebuilder:validation:MinProperties:=1 type RoleAssignmentFilter 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"` - - // roleRef is a reference to the ORC Role which this resource is associated with. + // roleRef filters by the referenced Role. // +optional RoleRef *KubernetesNameRef `json:"roleRef,omitempty"` - // userRef is a reference to the ORC User which this resource is associated with. + // userRef filters by the referenced User. // +optional UserRef *KubernetesNameRef `json:"userRef,omitempty"` - // groupRef is a reference to the ORC Group which this resource is associated with. + // groupRef filters by the referenced Group. // +optional GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` - // projectRef is a reference to the ORC Project which this resource is associated with. + // projectRef filters by the referenced Project scope. // +optional ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // domainRef is a reference to the ORC Domain which this resource is associated with. + // domainRef filters by the referenced Domain scope. // +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/roles } -// RoleAssignmentResourceStatus represents the observed state of the resource. +// RoleAssignmentResourceStatus represents the observed state of the role assignment. +// Note: Role assignments do not have a unique ID in OpenStack - they are identified +// by the combination of role, actor (user/group), and scope (project/domain). type RoleAssignmentResourceStatus 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"` - - // roleID is the ID of the Role to which the resource is associated. + // roleID is the OpenStack ID of the assigned role. // +kubebuilder:validation:MaxLength=1024 // +optional RoleID string `json:"roleID,omitempty"` - // userID is the ID of the User to which the resource is associated. + // userID is the OpenStack ID of the user (if actorType is User). // +kubebuilder:validation:MaxLength=1024 // +optional UserID string `json:"userID,omitempty"` - // groupID is the ID of the Group to which the resource is associated. + // groupID is the OpenStack ID of the group (if actorType is Group). // +kubebuilder:validation:MaxLength=1024 // +optional GroupID string `json:"groupID,omitempty"` - // projectID is the ID of the Project to which the resource is associated. + // projectID is the OpenStack ID of the project scope (if scopeType is Project). // +kubebuilder:validation:MaxLength=1024 // +optional ProjectID string `json:"projectID,omitempty"` - // domainID is the ID of the Domain to which the resource is associated. + // domainID is the OpenStack ID of the domain scope (if scopeType is Domain). // +kubebuilder:validation:MaxLength=1024 // +optional DomainID string `json:"domainID,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the RoleAssignment structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 09694efa4..a8f37b4e5 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3782,6 +3782,243 @@ func (in *Role) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignment) DeepCopyInto(out *RoleAssignment) { + *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 RoleAssignment. +func (in *RoleAssignment) DeepCopy() *RoleAssignment { + if in == nil { + return nil + } + out := new(RoleAssignment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleAssignment) 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 *RoleAssignmentFilter) DeepCopyInto(out *RoleAssignmentFilter) { + *out = *in + if in.RoleRef != nil { + in, out := &in.RoleRef, &out.RoleRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.GroupRef != nil { + in, out := &in.GroupRef, &out.GroupRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **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 RoleAssignmentFilter. +func (in *RoleAssignmentFilter) DeepCopy() *RoleAssignmentFilter { + if in == nil { + return nil + } + out := new(RoleAssignmentFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentImport) DeepCopyInto(out *RoleAssignmentImport) { + *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(RoleAssignmentFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentImport. +func (in *RoleAssignmentImport) DeepCopy() *RoleAssignmentImport { + if in == nil { + return nil + } + out := new(RoleAssignmentImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentList) DeepCopyInto(out *RoleAssignmentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RoleAssignment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentList. +func (in *RoleAssignmentList) DeepCopy() *RoleAssignmentList { + if in == nil { + return nil + } + out := new(RoleAssignmentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleAssignmentList) 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 *RoleAssignmentResourceSpec) DeepCopyInto(out *RoleAssignmentResourceSpec) { + *out = *in + if in.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.GroupRef != nil { + in, out := &in.GroupRef, &out.GroupRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **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 RoleAssignmentResourceSpec. +func (in *RoleAssignmentResourceSpec) DeepCopy() *RoleAssignmentResourceSpec { + if in == nil { + return nil + } + out := new(RoleAssignmentResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentResourceStatus) DeepCopyInto(out *RoleAssignmentResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentResourceStatus. +func (in *RoleAssignmentResourceStatus) DeepCopy() *RoleAssignmentResourceStatus { + if in == nil { + return nil + } + out := new(RoleAssignmentResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentSpec) DeepCopyInto(out *RoleAssignmentSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(RoleAssignmentImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(RoleAssignmentResourceSpec) + (*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 RoleAssignmentSpec. +func (in *RoleAssignmentSpec) DeepCopy() *RoleAssignmentSpec { + if in == nil { + return nil + } + out := new(RoleAssignmentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentStatus) DeepCopyInto(out *RoleAssignmentStatus) { + *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(RoleAssignmentResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentStatus. +func (in *RoleAssignmentStatus) DeepCopy() *RoleAssignmentStatus { + if in == nil { + return nil + } + out := new(RoleAssignmentStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RoleFilter) DeepCopyInto(out *RoleFilter) { *out = *in diff --git a/api/v1alpha1/zz_generated.roleassignment-resource.go b/api/v1alpha1/zz_generated.roleassignment-resource.go new file mode 100644 index 000000000..03d1eee39 --- /dev/null +++ b/api/v1alpha1/zz_generated.roleassignment-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" +) + +// RoleAssignmentImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type RoleAssignmentImport 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 *RoleAssignmentFilter `json:"filter,omitempty"` +} + +// RoleAssignmentSpec 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 RoleAssignmentSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *RoleAssignmentImport `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 *RoleAssignmentResourceSpec `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"` +} + +// RoleAssignmentStatus defines the observed state of an ORC resource. +type RoleAssignmentStatus 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 *RoleAssignmentResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &RoleAssignment{} + +func (i *RoleAssignment) 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" + +// RoleAssignment is the Schema for an ORC resource. +type RoleAssignment 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 RoleAssignmentSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status RoleAssignmentStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RoleAssignmentList contains a list of RoleAssignment. +type RoleAssignmentList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of RoleAssignment. + // +required + Items []RoleAssignment `json:"items"` +} + +func (l *RoleAssignmentList) GetItems() []RoleAssignment { + return l.Items +} + +func init() { + SchemeBuilder.Register(&RoleAssignment{}, &RoleAssignmentList{}) +} + +func (i *RoleAssignment) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &RoleAssignment{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index c8a624acc..8ec80848a 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -41,6 +41,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/project" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/role" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/roleassignment" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/router" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/routerinterface" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/securitygroup" @@ -136,6 +137,7 @@ func main() { keypair.New(scopeFactory), group.New(scopeFactory), role.New(scopeFactory), + roleassignment.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3e7cff7c6..b41b708d5 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -159,6 +159,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignment": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignment(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref), @@ -7190,6 +7198,381 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref common.Refere } } +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignment(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignment 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.RoleAssignmentSpec"), + }, + }, + "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.RoleAssignmentStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentFilter defines import filter criteria for existing role assignments.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "roleRef": { + SchemaProps: spec.SchemaProps{ + Description: "roleRef filters by the referenced Role.", + Type: []string{"string"}, + Format: "", + }, + }, + "userRef": { + SchemaProps: spec.SchemaProps{ + Description: "userRef filters by the referenced User.", + Type: []string{"string"}, + Format: "", + }, + }, + "groupRef": { + SchemaProps: spec.SchemaProps{ + Description: "groupRef filters by the referenced Group.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef filters by the referenced Project scope.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef filters by the referenced Domain scope.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentImport 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.RoleAssignmentFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentList contains a list of RoleAssignment.", + 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 RoleAssignment.", + 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.RoleAssignment"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignment", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentResourceSpec defines the desired role assignment. A role assignment grants a role to a user or group on a project or domain. Role assignments are immutable once created and identified by the combination of (role, actor, scope) rather than a separate ID.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "roleRef": { + SchemaProps: spec.SchemaProps{ + Description: "roleRef references the Role being assigned.", + Type: []string{"string"}, + Format: "", + }, + }, + "userRef": { + SchemaProps: spec.SchemaProps{ + Description: "userRef references the User receiving the role assignment. Exactly one of userRef or groupRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "groupRef": { + SchemaProps: spec.SchemaProps{ + Description: "groupRef references the Group receiving the role assignment. Exactly one of userRef or groupRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef references the Project scope for the assignment. Exactly one of projectRef or domainRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef references the Domain scope for the assignment. Exactly one of projectRef or domainRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"roleRef"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentResourceStatus represents the observed state of the role assignment. Note: Role assignments do not have a unique ID in OpenStack - they are identified by the combination of role, actor (user/group), and scope (project/domain).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "roleID": { + SchemaProps: spec.SchemaProps{ + Description: "roleID is the OpenStack ID of the assigned role.", + Type: []string{"string"}, + Format: "", + }, + }, + "userID": { + SchemaProps: spec.SchemaProps{ + Description: "userID is the OpenStack ID of the user (if actorType is User).", + Type: []string{"string"}, + Format: "", + }, + }, + "groupID": { + SchemaProps: spec.SchemaProps{ + Description: "groupID is the OpenStack ID of the group (if actorType is Group).", + Type: []string{"string"}, + Format: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the OpenStack ID of the project scope (if scopeType is Project).", + Type: []string{"string"}, + Format: "", + }, + }, + "domainID": { + SchemaProps: spec.SchemaProps{ + Description: "domainID is the OpenStack ID of the domain scope (if scopeType is Domain).", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentSpec 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.RoleAssignmentImport"), + }, + }, + "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.RoleAssignmentResourceSpec"), + }, + }, + "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.RoleAssignmentImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentStatus 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.RoleAssignmentResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(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 2ff83df6e..7e1bd4147 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -124,6 +124,10 @@ var resources []templateFields = []templateFields{ { Name: "Role", }, + { + Name: "RoleAssignment", + IsNotNamed: true, + }, { Name: "Router", ExistingOSClient: true, diff --git a/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml new file mode 100644 index 000000000..2c3123c35 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml @@ -0,0 +1,348 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: roleassignments.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: RoleAssignment + listKind: RoleAssignmentList + plural: roleassignments + singular: roleassignment + 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: RoleAssignment 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 filters by the referenced Domain scope. + maxLength: 253 + minLength: 1 + type: string + groupRef: + description: groupRef filters by the referenced Group. + maxLength: 253 + minLength: 1 + type: string + projectRef: + description: projectRef filters by the referenced Project + scope. + maxLength: 253 + minLength: 1 + type: string + roleRef: + description: roleRef filters by the referenced Role. + maxLength: 253 + minLength: 1 + type: string + userRef: + description: userRef filters by the referenced User. + maxLength: 253 + minLength: 1 + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + maxLength: 36 + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + domainRef: + description: |- + domainRef references the Domain scope for the assignment. + Exactly one of projectRef or domainRef must be specified. + maxLength: 253 + minLength: 1 + type: string + groupRef: + description: |- + groupRef references the Group receiving the role assignment. + Exactly one of userRef or groupRef must be specified. + maxLength: 253 + minLength: 1 + type: string + projectRef: + description: |- + projectRef references the Project scope for the assignment. + Exactly one of projectRef or domainRef must be specified. + maxLength: 253 + minLength: 1 + type: string + roleRef: + description: roleRef references the Role being assigned. + maxLength: 253 + minLength: 1 + type: string + userRef: + description: |- + userRef references the User receiving the role assignment. + Exactly one of userRef or groupRef must be specified. + maxLength: 253 + minLength: 1 + type: string + required: + - roleRef + type: object + x-kubernetes-validations: + - message: exactly one of userRef or groupRef is required + rule: (has(self.userRef) && !has(self.groupRef)) || (!has(self.userRef) + && has(self.groupRef)) + - message: exactly one of projectRef or domainRef is required + rule: (has(self.projectRef) && !has(self.domainRef)) || (!has(self.projectRef) + && has(self.domainRef)) + - message: RoleAssignmentResourceSpec is immutable + rule: self == oldSelf + 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: + domainID: + description: domainID is the OpenStack ID of the domain scope + (if scopeType is Domain). + maxLength: 1024 + type: string + groupID: + description: groupID is the OpenStack ID of the group (if actorType + is Group). + maxLength: 1024 + type: string + projectID: + description: projectID is the OpenStack ID of the project scope + (if scopeType is Project). + maxLength: 1024 + type: string + roleID: + description: roleID is the OpenStack ID of the assigned role. + maxLength: 1024 + type: string + userID: + description: userID is the OpenStack ID of the user (if actorType + is User). + 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 85a318b42..23338effb 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -16,6 +16,7 @@ resources: - bases/openstack.k-orc.cloud_ports.yaml - bases/openstack.k-orc.cloud_projects.yaml - bases/openstack.k-orc.cloud_roles.yaml +- bases/openstack.k-orc.cloud_roleassignments.yaml - bases/openstack.k-orc.cloud_routers.yaml - bases/openstack.k-orc.cloud_routerinterfaces.yaml - bases/openstack.k-orc.cloud_securitygroups.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3b67eb9fa..1f7a41a45 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -29,6 +29,7 @@ rules: - networks - ports - projects + - roleassignments - roles - routerinterfaces - routers @@ -64,6 +65,7 @@ rules: - networks/status - ports/status - projects/status + - roleassignments/status - roles/status - routerinterfaces/status - routers/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 4b86755db..b20a311e7 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -14,6 +14,7 @@ resources: - openstack_v1alpha1_port.yaml - openstack_v1alpha1_project.yaml - openstack_v1alpha1_role.yaml +- openstack_v1alpha1_roleassignment.yaml - openstack_v1alpha1_router.yaml - openstack_v1alpha1_routerinterface.yaml - openstack_v1alpha1_securitygroup.yaml diff --git a/config/samples/openstack_v1alpha1_roleassignment.yaml b/config/samples/openstack_v1alpha1_roleassignment.yaml index f1294f7ce..876c748b9 100644 --- a/config/samples/openstack_v1alpha1_roleassignment.yaml +++ b/config/samples/openstack_v1alpha1_roleassignment.yaml @@ -1,3 +1,38 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-sample-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-sample-role +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-sample-group +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-sample-group +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-sample-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-sample-project --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment @@ -5,10 +40,10 @@ metadata: name: roleassignment-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 RoleAssignment - # TODO(scaffolding): Add all fields the resource supports + roleRef: roleassignment-sample-role + groupRef: roleassignment-sample-group + projectRef: roleassignment-sample-project diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go index 32fafce63..deb43d62f 100644 --- a/internal/controllers/roleassignment/actuator.go +++ b/internal/controllers/roleassignment/actuator.go @@ -21,48 +21,65 @@ import ( "iter" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" - 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 = roles.RoleAssignment - - 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] -) +// OpenStack resource type +type osResourceT = roles.RoleAssignment type roleassignmentActuator struct { osClient osclients.RoleAssignmentClient k8sClient client.Client } -var _ createResourceActuator = roleassignmentActuator{} -var _ deleteResourceActuator = roleassignmentActuator{} +// GetResourceByComponents queries for the role assignment by its tuple (role, actor, scope). +// OpenStack doesn't assign IDs to role assignments - they're identified by this tuple. +// Exactly one of userID/groupID must be set, and exactly one of projectID/domainID must be set. +func (actuator roleassignmentActuator) GetResourceByComponents( + ctx context.Context, + roleID string, + userID string, + groupID string, + projectID string, + domainID string, +) (*osResourceT, progress.ReconcileStatus) { + // Build query options from components + listOpts := roles.ListAssignmentsOpts{ + RoleID: roleID, + // Note: Don't set Effective parameter - it can cause issues with group assignments + } -func (roleassignmentActuator) GetResourceID(osResource *osResourceT) string { - return osResource.ID -} + // Set actor (user OR group, never both) + if userID != "" { + listOpts.UserID = userID + } else if groupID != "" { + listOpts.GroupID = groupID + } -func (actuator roleassignmentActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { - resource, err := actuator.osClient.GetRoleAssignment(ctx, id) - if err != nil { - return nil, progress.WrapError(err) + // Set scope (project OR domain, never both) + if projectID != "" { + listOpts.ScopeProjectID = projectID + } else if domainID != "" { + listOpts.ScopeDomainID = domainID } - return resource, nil + + // Query with exact filters - should return exactly one result + for assignment, err := range actuator.osClient.ListRoleAssignments(ctx, listOpts) { + if err != nil { + return nil, progress.WrapError(err) + } + return assignment, nil + } + + // Not found + return nil, nil } func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { @@ -71,75 +88,181 @@ func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Co 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 + // Fetch all dependencies to build the exact filter + var roleID, userID, groupID, projectID, domainID string + + // Role dependency (required) + role, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.RoleRef, "Role", + func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if role == nil { + return nil, false // Not ready + } + roleID = ptr.Deref(role.Status.ID, "") + + // Actor dependency (user XOR group) + if resourceSpec.UserRef != nil { + user, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.UserRef, "User", + func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if user == nil { + return nil, false // Not ready + } + userID = ptr.Deref(user.Status.ID, "") + } else { + group, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.GroupRef, "Group", + func(dep *orcv1alpha1.Group) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if group == nil { + return nil, false // Not ready + } + groupID = ptr.Deref(group.Status.ID, "") + } + + // Scope dependency (project XOR domain) + if resourceSpec.ProjectRef != nil { + project, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if project == nil { + return nil, false // Not ready + } + projectID = ptr.Deref(project.Status.ID, "") + } else { + domain, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if domain == nil { + return nil, false // Not ready + } + domainID = ptr.Deref(domain.Status.ID, "") + } + + // Build query - only set fields that have values + listOpts := roles.ListAssignmentsOpts{ + RoleID: roleID, + // Note: Don't set Effective parameter - it can cause issues with group assignments + } + + // Set actor (user OR group, never both) + if userID != "" { + listOpts.UserID = userID + } else if groupID != "" { + listOpts.GroupID = groupID + } - listOpts := roles.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + // Set scope (project OR domain, never both) + if projectID != "" { + listOpts.ScopeProjectID = projectID + } else if domainID != "" { + listOpts.ScopeDomainID = domainID } return actuator.osClient.ListRoleAssignments(ctx, listOpts), true } func (actuator roleassignmentActuator) 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 - role, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.RoleRef, "Role", - func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + // Build ListAssignmentsOpts from filter references + var roleID, userID, groupID, projectID, domainID string - user, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.UserRef, "User", - func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if filter.RoleRef != nil { + role, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.RoleRef, "Role", + func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if role != nil && role.Status.ID != nil { + roleID = *role.Status.ID + } + } - group, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.GroupRef, "Group", - func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if filter.UserRef != nil { + user, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.UserRef, "User", + func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if user != nil && user.Status.ID != nil { + userID = *user.Status.ID + } + } - 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 filter.GroupRef != nil { + group, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.GroupRef, "Group", + func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if group != nil && group.Status.ID != nil { + groupID = *group.Status.ID + } + } - 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 filter.ProjectRef != nil { + 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 project != nil && project.Status.ID != nil { + projectID = *project.Status.ID + } + } + + if filter.DomainRef != nil { + 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 domain != nil && domain.Status.ID != nil { + domainID = *domain.Status.ID + } + } if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } - listOpts := roles.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - RoleID: ptr.Deref(role.Status.ID, ""), - UserID: ptr.Deref(user.Status.ID, ""), - GroupID: ptr.Deref(group.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), - DomainID: ptr.Deref(domain.Status.ID, ""), - // TODO(scaffolding): Add more import filters + // Build query - only set fields that have values (filter fields are optional) + listOpts := roles.ListAssignmentsOpts{ + // Note: Don't set Effective parameter - it can cause issues with group assignments } - return actuator.osClient.ListRoleAssignments(ctx, listOpts), reconcileStatus + if roleID != "" { + listOpts.RoleID = roleID + } + if userID != "" { + listOpts.UserID = userID + } + if groupID != "" { + listOpts.GroupID = groupID + } + if projectID != "" { + listOpts.ScopeProjectID = projectID + } + if domainID != "" { + listOpts.ScopeDomainID = domainID + } + + return actuator.osClient.ListRoleAssignments(ctx, listOpts), nil } func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -152,18 +275,20 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o } var reconcileStatus progress.ReconcileStatus + // Fetch role dependency (required) + role, roleDepRS := roleDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(roleDepRS) var roleID string - role, roleDepRS := roleDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(roleDepRS) - if role != nil { - roleID = ptr.Deref(role.Status.ID, "") - } - - var userID string + if role != nil { + roleID = ptr.Deref(role.Status.ID, "") + } + + // Fetch actor dependency (user XOR group) + var userID, groupID string if resource.UserRef != nil { user, userDepRS := userDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { @@ -174,10 +299,7 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o if user != nil { userID = ptr.Deref(user.Status.ID, "") } - } - - var groupID string - if resource.GroupRef != nil { + } else { group, groupDepRS := groupDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil @@ -189,7 +311,8 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o } } - var projectID string + // Fetch scope dependency (project XOR domain) + var projectID, domainID string if resource.ProjectRef != nil { project, projectDepRS := projectDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool { @@ -200,10 +323,7 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o if project != nil { projectID = ptr.Deref(project.Status.ID, "") } - } - - var domainID string - if resource.DomainRef != nil { + } else { domain, domainDepRS := domainDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil @@ -214,146 +334,70 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o domainID = ptr.Deref(domain.Status.ID, "") } } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } - createOpts := roles.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - RoleID: roleID, - UserID: userID, - GroupID: groupID, - ProjectID: projectID, + + // Build AssignOpts + assignOpts := roles.AssignOpts{ + UserID: userID, + GroupID: groupID, + ProjectID: projectID, DomainID: domainID, - // TODO(scaffolding): Add more fields } - osResource, err := actuator.osClient.CreateRoleAssignment(ctx, createOpts) + // Assign the role (idempotent - returns 204 even if already exists) + err := actuator.osClient.AssignRole(ctx, roleID, assignOpts) 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) + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating role assignment: "+err.Error(), err) } return nil, progress.WrapError(err) } - return osResource, nil -} - -func (actuator roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - return progress.WrapError(actuator.osClient.DeleteRoleAssignment(ctx, resource.ID)) -} - -func (actuator roleassignmentActuator) 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 := roles.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)) + // Verify the assignment was created by listing with exact filters + listOpts := roles.ListAssignmentsOpts{ + RoleID: roleID, + // Note: Don't set Effective parameter - it can cause issues with group assignments } - if !needsUpdate { - log.V(logging.Debug).Info("No changes") - return nil - } - - _, err = actuator.osClient.UpdateRoleAssignment(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) + // Set actor (user OR group, never both) + if userID != "" { + listOpts.UserID = userID + } else if groupID != "" { + listOpts.GroupID = groupID } - if err != nil { - return progress.WrapError(err) + // Set scope (project OR domain, never both) + if projectID != "" { + listOpts.ScopeProjectID = projectID + } else if domainID != "" { + listOpts.ScopeDomainID = domainID } - return progress.NeedsRefresh() -} - -func needsUpdate(updateOpts roles.UpdateOpts) (bool, error) { - updateOptsMap, err := updateOpts.ToRoleAssignmentUpdateMap() - if err != nil { - return false, err - } - - updateMap, ok := updateOptsMap["role_assignment"].(map[string]any) - if !ok { - updateMap = make(map[string]any) + // Get the first matching assignment to return + for assignment, err := range actuator.osClient.ListRoleAssignments(ctx, listOpts) { + if err != nil { + return nil, progress.WrapError(err) + } + return assignment, nil } - return len(updateMap) > 0, nil + // This shouldn't happen - we just assigned it + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "role assignment succeeded but could not be found in OpenStack")) } -func handleNameUpdate(updateOpts *roles.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { - name := getResourceName(obj) - if osResource.Name != name { - updateOpts.Name = &name +func (actuator roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + // Build UnassignOpts from the osResource + unassignOpts := roles.UnassignOpts{ + UserID: osResource.User.ID, + GroupID: osResource.Group.ID, + ProjectID: osResource.Scope.Project.ID, + DomainID: osResource.Scope.Domain.ID, } -} - -func handleDescriptionUpdate(updateOpts *roles.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - description := ptr.Deref(resource.Description, "") - if osResource.Description != description { - updateOpts.Description = &description - } -} - -func (actuator roleassignmentActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { - return []resourceReconciler{ - actuator.updateResource, - }, nil -} - -type roleassignmentHelperFactory struct{} - -var _ helperFactory = roleassignmentHelperFactory{} - -func newActuator(ctx context.Context, orcObject *orcv1alpha1.RoleAssignment, controller interfaces.ResourceController) (roleassignmentActuator, 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 roleassignmentActuator{}, reconcileStatus - } - - clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) - if err != nil { - return roleassignmentActuator{}, progress.WrapError(err) - } - osClient, err := clientScope.NewRoleAssignmentClient() - if err != nil { - return roleassignmentActuator{}, progress.WrapError(err) - } - - return roleassignmentActuator{ - osClient: osClient, - k8sClient: controller.GetK8sClient(), - }, nil -} - -func (roleassignmentHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { - return roleassignmentAdapter{obj} -} - -func (roleassignmentHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { - return newActuator(ctx, orcObject, controller) -} -func (roleassignmentHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { - return newActuator(ctx, orcObject, controller) + return progress.WrapError(actuator.osClient.UnassignRole(ctx, osResource.Role.ID, unassignOpts)) } diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go index 0433ecf35..c37d536c8 100644 --- a/internal/controllers/roleassignment/actuator_test.go +++ b/internal/controllers/roleassignment/actuator_test.go @@ -16,104 +16,5 @@ limitations under the License. package roleassignment -import ( - "testing" - - "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" - 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 roles.UpdateOpts - expectChange bool - }{ - { - name: "Empty base opts", - updateOpts: roles.UpdateOpts{}, - expectChange: false, - }, - { - name: "Updated opts", - updateOpts: roles.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.RoleAssignment{} - resource.Name = "object-name" - resource.Spec = orcv1alpha1.RoleAssignmentSpec{ - Resource: &orcv1alpha1.RoleAssignmentResourceSpec{Name: tt.newValue}, - } - osResource := &osResourceT{Name: tt.existingValue} - - updateOpts := roles.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.RoleAssignmentResourceSpec{Description: tt.newValue} - osResource := &osResourceT{Description: tt.existingValue} - - updateOpts := roles.UpdateOpts{} - handleDescriptionUpdate(&updateOpts, resource, osResource) - - got, _ := needsUpdate(updateOpts) - if got != tt.expectChange { - t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) - } - }) - - } -} +// NOTE: RoleAssignment is fully immutable, so there are no update functions to test. +// Tests for creation and deletion logic should be added as KUTTL E2E tests in the tests/ directory. diff --git a/internal/controllers/roleassignment/controller.go b/internal/controllers/roleassignment/controller.go index 631ea1236..9051fc97f 100644 --- a/internal/controllers/roleassignment/controller.go +++ b/internal/controllers/roleassignment/controller.go @@ -27,7 +27,6 @@ import ( 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" @@ -277,6 +276,10 @@ func (c roleassignmentReconcilerConstructor) SetupWithManager(ctx context.Contex return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, roleassignmentHelperFactory{}, roleassignmentStatusWriter{}) - return builder.Complete(&r) + // Custom reconciler for role assignments (relationships, not resources with IDs) + reconciler := &roleassignmentReconciler{ + client: mgr.GetClient(), + scopeFactory: c.scopeFactory, + } + return builder.Complete(reconciler) } diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go new file mode 100644 index 000000000..8084a6efd --- /dev/null +++ b/internal/controllers/roleassignment/reconciler.go @@ -0,0 +1,386 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 roleassignment + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/status" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/scope" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/applyconfigs" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/finalizers" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" + orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +// roleassignmentReconciler reconciles RoleAssignment objects. +// Unlike other ORC resources, role assignments are relationships (not resources with IDs), +// so this uses a custom reconciler instead of the generic framework. +type roleassignmentReconciler struct { + client client.Client + scopeFactory scope.Factory +} + +// Reconcile is the main entry point for reconciliation. +// It fetches the RoleAssignment object and routes to either reconcileNormal or reconcileDelete. +func (r *roleassignmentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + orcObject := new(orcObjectT) + err := r.client.Get(ctx, req.NamespacedName, orcObject) + if err != nil { + if apierrors.IsNotFound(err) { + // Object deleted, nothing to do + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + log := ctrl.LoggerFrom(ctx) + + // Check if object is being deleted + if !orcObject.GetDeletionTimestamp().IsZero() { + return r.reconcileDelete(ctx, orcObject).Return(log) + } + + return r.reconcileNormal(ctx, orcObject).Return(log) +} + +// shouldReconcile determines if reconciliation should proceed based on the Progressing condition. +// Returns true if: +// - Progressing condition is not present +// - Progressing condition is True +// - Progressing condition is False but observedGeneration is stale +func shouldReconcile(obj orcObjectPT) bool { + progressing := meta.FindStatusCondition(obj.GetConditions(), orcv1alpha1.ConditionProgressing) + if progressing == nil { + return true + } + + if progressing.Status == metav1.ConditionTrue { + return true + } + + return progressing.ObservedGeneration != obj.GetGeneration() +} + +// reconcileNormal handles the normal reconciliation flow: +// 1. Check if we should reconcile (based on Progressing condition) +// 2. Create actuator (OpenStack client) +// 3. Get or create the role assignment +// 4. Update status +func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObject orcObjectPT) (reconcileStatus progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + + // Check if we should skip reconciliation + if !shouldReconcile(orcObject) { + log.V(logging.Verbose).Info("Status is up to date: not reconciling") + return reconcileStatus + } + + log.V(logging.Verbose).Info("Reconciling role assignment") + + var osResource *osResourceT + + // Ensure we always update status at the end + defer func() { + reconcileStatus = reconcileStatus.WithReconcileStatus( + r.updateStatus(ctx, orcObject, osResource, reconcileStatus)) + }() + + // Phase 3: Add finalizer if not present + finalizer := orcstrings.GetFinalizerName(controllerName) + if !controllerutil.ContainsFinalizer(orcObject, finalizer) { + patch := finalizers.SetFinalizerPatch(orcObject, finalizer) + if err := r.client.Patch(ctx, orcObject, patch, client.ForceOwnership, orcstrings.GetSSAFieldOwnerWithTxn(controllerName, orcstrings.SSATransactionFinalizer)); err != nil { + return progress.WrapError(fmt.Errorf("setting finalizer: %w", err)) + } + } + + // Phase 3: Create actuator + actuator, actuatorRS := r.newActuator(ctx, orcObject) + if needsReschedule, err := actuatorRS.NeedsReschedule(); needsReschedule { + if err == nil { + log.V(logging.Verbose).Info("Waiting on events before creation") + } + return actuatorRS.WithReconcileStatus(reconcileStatus) + } + + // Phase 4: Check if role assignment exists using Status.Resource components + if orcObject.Status.Resource != nil { + statusResource := orcObject.Status.Resource + // If we have all components in status, try to fetch the role assignment + if statusResource.RoleID != "" && + (statusResource.UserID != "" || statusResource.GroupID != "") && + (statusResource.ProjectID != "" || statusResource.DomainID != "") { + + osResource, getRS := actuator.GetResourceByComponents( + ctx, + statusResource.RoleID, + statusResource.UserID, + statusResource.GroupID, + statusResource.ProjectID, + statusResource.DomainID, + ) + if needsReschedule, err := getRS.NeedsReschedule(); needsReschedule { + if orcerrors.IsNotFound(err) { + // Resource we previously created has been deleted unexpectedly + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "role assignment has been deleted from OpenStack")) + } + return getRS.WithReconcileStatus(reconcileStatus) + } + + if osResource != nil { + log.V(logging.Verbose).Info("Got existing role assignment") + return reconcileStatus + } + } + } + + // Phase 5: Fetch dependencies and create role assignment + osResource, createRS := actuator.CreateResource(ctx, orcObject) + if needsReschedule, err := createRS.NeedsReschedule(); needsReschedule { + if err == nil { + log.V(logging.Verbose).Info("Waiting on dependencies or creation") + } + return createRS.WithReconcileStatus(reconcileStatus) + } + + if osResource == nil { + return reconcileStatus.WithError(fmt.Errorf("osResource is not set, but no wait events or error")) + } + + log.V(logging.Info).Info("Role assignment created or adopted") + return reconcileStatus +} + +// reconcileDelete handles deletion of the RoleAssignment: +// 1. Check finalizer +// 2. Fetch the role assignment (using Status.Resource components) +// 3. Check management policy +// 4. Delete from OpenStack +// 5. Remove finalizer +func (r *roleassignmentReconciler) reconcileDelete(ctx context.Context, orcObject orcObjectPT) (reconcileStatus progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + log.V(logging.Verbose).Info("Reconciling role assignment delete") + + var osResource *osResourceT + deleted := false + + // Update status unless we've removed the finalizer + defer func() { + if !deleted { + reconcileStatus = reconcileStatus.WithReconcileStatus( + r.updateStatus(ctx, orcObject, osResource, reconcileStatus)) + } + }() + + finalizer := orcstrings.GetFinalizerName(controllerName) + + // Check if our finalizer is present + var foundFinalizer bool + for _, f := range orcObject.GetFinalizers() { + if f == finalizer { + foundFinalizer = true + } else { + reconcileStatus = reconcileStatus.WaitingOnFinalizer(f) + } + } + + // Cleanup not required if our finalizer is not present + if !foundFinalizer { + return reconcileStatus + } + + if needsReschedule, err := reconcileStatus.NeedsReschedule(); needsReschedule { + if err == nil { + log.V(logging.Verbose).Info("Deferring resource cleanup due to remaining external finalizers") + } + return reconcileStatus + } + + removeFinalizer := func(reconcileStatus progress.ReconcileStatus) progress.ReconcileStatus { + if err := r.client.Patch(ctx, orcObject, finalizers.RemoveFinalizerPatch(orcObject), orcstrings.GetSSAFieldOwnerWithTxn(controllerName, orcstrings.SSATransactionFinalizer)); err != nil { + return reconcileStatus.WithError(fmt.Errorf("removing finalizer: %w", err)) + } + deleted = true + return reconcileStatus + } + + // Check management policy + managementPolicy := orcObject.Spec.ManagementPolicy + managedOptions := orcObject.Spec.ManagedOptions + if managementPolicy == orcv1alpha1.ManagementPolicyUnmanaged || managedOptions.GetOnDelete() == orcv1alpha1.OnDeleteDetach { + logPolicy := []any{"managementPolicy", managementPolicy} + if managementPolicy == orcv1alpha1.ManagementPolicyManaged { + logPolicy = append(logPolicy, "onDelete", managedOptions.GetOnDelete()) + } + log.V(logging.Verbose).Info("Not deleting OpenStack resource due to policy", logPolicy...) + return removeFinalizer(reconcileStatus) + } + + // Fetch the role assignment using Status.Resource components + if orcObject.Status.Resource != nil { + statusResource := orcObject.Status.Resource + if statusResource.RoleID != "" && + (statusResource.UserID != "" || statusResource.GroupID != "") && + (statusResource.ProjectID != "" || statusResource.DomainID != "") { + + // Create actuator for deletion + actuator, actuatorRS := r.newActuator(ctx, orcObject) + if needsReschedule, err := actuatorRS.NeedsReschedule(); needsReschedule { + if err == nil { + log.V(logging.Verbose).Info("Waiting on events before deletion") + } + return actuatorRS.WithReconcileStatus(reconcileStatus) + } + + osResource, getRS := actuator.GetResourceByComponents( + ctx, + statusResource.RoleID, + statusResource.UserID, + statusResource.GroupID, + statusResource.ProjectID, + statusResource.DomainID, + ) + if needsReschedule, err := getRS.NeedsReschedule(); needsReschedule { + // NotFound is our success condition for delete + if err == nil || !orcerrors.IsNotFound(err) { + return getRS.WithReconcileStatus(reconcileStatus) + } + osResource = nil + } + + if osResource != nil { + log.V(logging.Info).Info("Deleting role assignment from OpenStack") + deleteRS := actuator.DeleteResource(ctx, orcObject, osResource) + if needsReschedule, _ := deleteRS.NeedsReschedule(); needsReschedule { + return deleteRS.WithReconcileStatus(reconcileStatus) + } + } + } + } + + log.V(logging.Info).Info("Role assignment deletion confirmed") + return removeFinalizer(reconcileStatus) +} + +// newActuator creates a roleassignmentActuator with OpenStack client setup. +func (r *roleassignmentReconciler) newActuator(ctx context.Context, orcObject orcObjectPT) (roleassignmentActuator, progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + + // Ensure credential secrets exist and have our finalizer + _, reconcileStatus := credentialsDependency.GetDependencies(ctx, r.client, orcObject, func(*corev1.Secret) bool { return true }) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return roleassignmentActuator{}, reconcileStatus + } + + clientScope, err := r.scopeFactory.NewClientScopeFromObject(ctx, r.client, log, orcObject) + if err != nil { + return roleassignmentActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewRoleAssignmentClient() + if err != nil { + return roleassignmentActuator{}, progress.WrapError(err) + } + + return roleassignmentActuator{ + osClient: osClient, + k8sClient: r.client, + }, nil +} + +// updateStatus writes the observed state back to the Kubernetes object. +// This sets Status.Resource fields and conditions (Available, Progressing). +// Note: Status.ID is intentionally left nil for role assignments. +func (r *roleassignmentReconciler) updateStatus( + ctx context.Context, + orcObject orcObjectPT, + osResource *osResourceT, + reconcileStatus progress.ReconcileStatus, +) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + now := metav1.NewTime(time.Now()) + + // Create apply configuration for status + statusApply := orcapplyconfigv1alpha1.RoleAssignmentStatus() + applyConfig := orcapplyconfigv1alpha1.RoleAssignment(orcObject.Name, orcObject.Namespace). + WithUID(orcObject.GetUID()). + WithStatus(statusApply) + + // Write resource status fields from osResource + if osResource != nil { + resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus() + + if osResource.Role.ID != "" { + resourceStatus.WithRoleID(osResource.Role.ID) + } + if osResource.User.ID != "" { + resourceStatus.WithUserID(osResource.User.ID) + } + if osResource.Group.ID != "" { + resourceStatus.WithGroupID(osResource.Group.ID) + } + if osResource.Scope.Project.ID != "" { + resourceStatus.WithProjectID(osResource.Scope.Project.ID) + } + if osResource.Scope.Domain.ID != "" { + resourceStatus.WithDomainID(osResource.Scope.Domain.ID) + } + + statusApply.WithResource(resourceStatus) + } + + // Determine Available status + availableStatus := metav1.ConditionFalse + if osResource != nil { + availableStatus = metav1.ConditionTrue + } else if orcObject.Status.Resource != nil && + (orcObject.Status.Resource.RoleID != "" || + orcObject.Status.Resource.UserID != "" || + orcObject.Status.Resource.GroupID != "" || + orcObject.Status.Resource.ProjectID != "" || + orcObject.Status.Resource.DomainID != "") { + availableStatus = metav1.ConditionUnknown + } + + // Set common conditions (Available and Progressing) + status.SetCommonConditions(orcObject, statusApply, availableStatus, reconcileStatus, now) + + // Patch status + ssaFieldOwner := orcstrings.GetSSAFieldOwnerWithTxn(controllerName, orcstrings.SSATransactionStatus) + if err := r.client.Status().Patch(ctx, orcObject, applyconfigs.Patch(types.ApplyPatchType, applyConfig), client.ForceOwnership, ssaFieldOwner); err != nil { + return progress.WrapError(fmt.Errorf("patching status: %w", err)) + } + + log.V(logging.Debug).Info("Updated status") + return nil +} diff --git a/internal/controllers/roleassignment/status.go b/internal/controllers/roleassignment/status.go index a3a720369..c7587b320 100644 --- a/internal/controllers/roleassignment/status.go +++ b/internal/controllers/roleassignment/status.go @@ -37,31 +37,47 @@ func (roleassignmentStatusWriter) GetApplyConfig(name, namespace string) *object return orcapplyconfigv1alpha1.RoleAssignment(name, namespace) } +// ResourceAvailableStatus returns the availability status of the role assignment. +// Role assignments don't have Status.ID, so we just check if osResource exists. func (roleassignmentStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.RoleAssignment, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { if osResource == nil { - if orcObject.Status.ID == nil { - return metav1.ConditionFalse, nil - } else { + // Check if we have any status IDs set (indicates we may have created it but can't find it) + if orcObject.Status.Resource != nil && + (orcObject.Status.Resource.RoleID != "" || + orcObject.Status.Resource.UserID != "" || + orcObject.Status.Resource.GroupID != "" || + orcObject.Status.Resource.ProjectID != "" || + orcObject.Status.Resource.DomainID != "") { return metav1.ConditionUnknown, nil } + return metav1.ConditionFalse, nil } return metav1.ConditionTrue, nil } +// ApplyResourceStatus extracts the role assignment details and applies them to status. func (roleassignmentStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { - resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus(). - WithRoleID(osResource.RoleID). - WithUserID(osResource.UserID). - WithGroupID(osResource.GroupID). - WithProjectID(osResource.ProjectID). - WithDomainID(osResource.DomainID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the RoleAssignmentResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional - - if osResource.Description != "" { - resourceStatus.WithDescription(osResource.Description) + resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus() + + // Extract role ID + if osResource.Role.ID != "" { + resourceStatus.WithRoleID(osResource.Role.ID) + } + + // Extract actor ID (user XOR group) + if osResource.User.ID != "" { + resourceStatus.WithUserID(osResource.User.ID) + } + if osResource.Group.ID != "" { + resourceStatus.WithGroupID(osResource.Group.ID) + } + + // Extract scope ID (project XOR domain) + if osResource.Scope.Project.ID != "" { + resourceStatus.WithProjectID(osResource.Scope.Project.ID) + } + if osResource.Scope.Domain.ID != "" { + resourceStatus.WithDomainID(osResource.Scope.Domain.ID) } statusApply.WithResource(resourceStatus) diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml deleted file mode 100644 index 7da99c8d0..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml +++ /dev/null @@ -1,53 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-create-full -status: - resource: - name: roleassignment-create-full-override - description: RoleAssignment 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: RoleAssignment - name: roleassignment-create-full - ref: roleassignment - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-create-full - ref: role - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: roleassignment-create-full - ref: user - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group - name: roleassignment-create-full - ref: group - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: roleassignment-create-full - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: roleassignment-create-full - ref: domain -assertAll: - - celExpr: "roleassignment.status.id != ''" - - celExpr: "roleassignment.status.resource.roleID == role.status.id" - - celExpr: "roleassignment.status.resource.userID == user.status.id" - - celExpr: "roleassignment.status.resource.groupID == group.status.id" - - celExpr: "roleassignment.status.resource.projectID == project.status.id" - - celExpr: "roleassignment.status.resource.domainID == domain.status.id" - # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml deleted file mode 100644 index ce3174c84..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml +++ /dev/null @@ -1,85 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: roleassignment-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: Group -metadata: - name: roleassignment-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: roleassignment-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: Domain -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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: roleassignment-create-full-override - description: RoleAssignment from "create full" test - roleRef: roleassignment-create-full - userRef: roleassignment-create-full - groupRef: roleassignment-create-full - projectRef: roleassignment-create-full - domainRef: roleassignment-create-full - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md deleted file mode 100644 index 74756b176..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Create a RoleAssignment with all the options - -## Step 00 - -Create a RoleAssignment 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/roleassignment/tests/roleassignment-create-group-domain/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-assert.yaml new file mode 100644 index 000000000..594f22e3f --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-assert.yaml @@ -0,0 +1,75 @@ +--- +# Assert Role is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gd-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Group is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gd-test-group +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Domain is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-gd-test-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert RoleAssignment is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-group-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Validate RoleAssignment status fields +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-group-domain + ref: roleassignment +assertAll: + # Verify status.id is NOT set (role assignments use component-based identification) + - celExpr: "!has(roleassignment.status.id) || roleassignment.status.id == ''" + # Verify all component IDs are populated in status.resource + - celExpr: "roleassignment.status.resource.roleID != ''" + - celExpr: "roleassignment.status.resource.groupID != ''" + - celExpr: "roleassignment.status.resource.domainID != ''" + # Verify user and project are not set (since we used group and domain) + - celExpr: "!has(roleassignment.status.resource.userID) || roleassignment.status.resource.userID == ''" + - celExpr: "!has(roleassignment.status.resource.projectID) || roleassignment.status.resource.projectID == ''" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-create-resource.yaml new file mode 100644 index 000000000..a0fae8d95 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-create-resource.yaml @@ -0,0 +1,54 @@ +--- +# Create a test role +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gd-test-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gd-test-role +--- +# Create a test group +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gd-test-group +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gd-test-group +--- +# Create a test domain +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-gd-test-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gd-test-domain +--- +# Create role assignment (group on domain) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-group-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-gd-test-role + groupRef: roleassignment-gd-test-group + domainRef: roleassignment-gd-test-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-secret.yaml similarity index 98% rename from internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml rename to internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-secret.yaml index 045711ee7..f0fb63e85 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/00-secret.yaml @@ -1,4 +1,3 @@ ---- apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-assert.yaml new file mode 100644 index 000000000..5470f499b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-assert.yaml @@ -0,0 +1,9 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-gd-test-domain + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-disable-domain.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-disable-domain.yaml new file mode 100644 index 000000000..fccf56038 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/01-disable-domain.yaml @@ -0,0 +1,7 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-gd-test-domain +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-assert.yaml new file mode 100644 index 000000000..64fc769bf --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-assert.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-create-group-domain --namespace $NAMESPACE" + skipLogOutput: true +--- +# Verify dependencies still exist (deletion guard should keep them) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gd-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gd-test-group +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-gd-test-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-delete-roleassignment.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-delete-roleassignment.yaml new file mode 100644 index 000000000..e3554c56b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/02-delete-roleassignment.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-group-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/README.md new file mode 100644 index 000000000..ec03542bb --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-domain/README.md @@ -0,0 +1,15 @@ +# Create a RoleAssignment for Group on Domain + +## Step 00 + +Create dependencies (Role, Group, Domain) and a RoleAssignment that assigns a role to a group on a domain. + +Verify that the observed state corresponds to the spec and the role assignment exists in OpenStack. + +## Step 01 + +Delete the RoleAssignment and verify it's removed from OpenStack. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-assert.yaml new file mode 100644 index 000000000..680b4e462 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-assert.yaml @@ -0,0 +1,75 @@ +--- +# Assert Role is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gp-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Group is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gp-test-group +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Project is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-gp-test-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert RoleAssignment is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-group-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Validate RoleAssignment status fields +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-group-project + ref: roleassignment +assertAll: + # Verify status.id is NOT set (role assignments use component-based identification) + - celExpr: "!has(roleassignment.status.id) || roleassignment.status.id == ''" + # Verify all component IDs are populated in status.resource + - celExpr: "roleassignment.status.resource.roleID != ''" + - celExpr: "roleassignment.status.resource.groupID != ''" + - celExpr: "roleassignment.status.resource.projectID != ''" + # Verify user and domain are not set (since we used group and project) + - celExpr: "!has(roleassignment.status.resource.userID) || roleassignment.status.resource.userID == ''" + - celExpr: "!has(roleassignment.status.resource.domainID) || roleassignment.status.resource.domainID == ''" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-create-resource.yaml new file mode 100644 index 000000000..229f73ac2 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-create-resource.yaml @@ -0,0 +1,54 @@ +--- +# Create a test role +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gp-test-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gp-test-role +--- +# Create a test group +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gp-test-group +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gp-test-group +--- +# Create a test project +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-gp-test-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gp-test-project +--- +# Create role assignment (group on project) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-group-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-gp-test-role + groupRef: roleassignment-gp-test-group + projectRef: roleassignment-gp-test-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-secret.yaml similarity index 98% rename from internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml rename to internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-secret.yaml index 045711ee7..f0fb63e85 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/00-secret.yaml @@ -1,4 +1,3 @@ ---- apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-assert.yaml new file mode 100644 index 000000000..774133d60 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-assert.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-create-group-project --namespace $NAMESPACE" + skipLogOutput: true +--- +# Verify dependencies still exist (deletion guard should keep them) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gp-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gp-test-group +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-gp-test-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-delete-roleassignment.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-delete-roleassignment.yaml new file mode 100644 index 000000000..3e76e9a0f --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/01-delete-roleassignment.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-group-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-group-project/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/README.md new file mode 100644 index 000000000..5ea31a789 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-group-project/README.md @@ -0,0 +1,15 @@ +# Create a RoleAssignment for Group on Project + +## Step 00 + +Create dependencies (Role, Group, Project) and a RoleAssignment that assigns a role to a group on a project. + +Verify that the observed state corresponds to the spec and the role assignment exists in OpenStack. + +## Step 01 + +Delete the RoleAssignment and verify it's removed from OpenStack. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml deleted file mode 100644 index 4106e13f4..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-create-minimal -status: - resource: - name: roleassignment-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: RoleAssignment - name: roleassignment-create-minimal - ref: roleassignment - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-create-minimal - ref: role -assertAll: - - celExpr: "roleassignment.status.id != ''" - - celExpr: "roleassignment.status.resource.roleID == role.status.id" - # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml deleted file mode 100644 index 28bf9a7aa..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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: - roleRef: roleassignment-create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml deleted file mode 100644 index c4d367d4a..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -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/roleassignment' in secret.metadata.finalizers" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml deleted file mode 100644 index 1620791b9..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -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/roleassignment/tests/roleassignment-create-minimal/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md deleted file mode 100644 index 08f0e40c6..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Create a RoleAssignment with the minimum options - -## Step 00 - -Create a minimal RoleAssignment, 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/roleassignment/tests/roleassignment-create-user-domain/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-assert.yaml new file mode 100644 index 000000000..9907fe691 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-assert.yaml @@ -0,0 +1,75 @@ +--- +# Assert Role is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-ud-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert User is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-ud-test-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Domain is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-ud-test-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert RoleAssignment is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-user-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Validate RoleAssignment status fields +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-user-domain + ref: roleassignment +assertAll: + # Verify status.id is NOT set (role assignments use component-based identification) + - celExpr: "!has(roleassignment.status.id) || roleassignment.status.id == ''" + # Verify all component IDs are populated in status.resource + - celExpr: "roleassignment.status.resource.roleID != ''" + - celExpr: "roleassignment.status.resource.userID != ''" + - celExpr: "roleassignment.status.resource.domainID != ''" + # Verify group and project are not set (since we used user and domain) + - celExpr: "!has(roleassignment.status.resource.groupID) || roleassignment.status.resource.groupID == ''" + - celExpr: "!has(roleassignment.status.resource.projectID) || roleassignment.status.resource.projectID == ''" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-create-resource.yaml new file mode 100644 index 000000000..844b43af0 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-create-resource.yaml @@ -0,0 +1,54 @@ +--- +# Create a test role +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-ud-test-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-ud-test-role +--- +# Create a test user +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-ud-test-user +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-ud-test-user +--- +# Create a test domain +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-ud-test-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-ud-test-domain +--- +# Create the role assignment +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-user-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-ud-test-role + userRef: roleassignment-ud-test-user + domainRef: roleassignment-ud-test-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-secret.yaml similarity index 98% rename from internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml rename to internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-secret.yaml index 045711ee7..f0fb63e85 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/00-secret.yaml @@ -1,4 +1,3 @@ ---- apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-assert.yaml new file mode 100644 index 000000000..49610f398 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-assert.yaml @@ -0,0 +1,9 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-ud-test-domain + ref: domain +assertAll: + - celExpr: "domain.status.resource.enabled == false" \ No newline at end of file diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-disable-domain.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-disable-domain.yaml new file mode 100644 index 000000000..09053901a --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/01-disable-domain.yaml @@ -0,0 +1,7 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-ud-test-domain +spec: + resource: + enabled: false \ No newline at end of file diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-assert.yaml new file mode 100644 index 000000000..fee740b41 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-assert.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-create-user-domain --namespace $NAMESPACE" + skipLogOutput: true +--- +# Verify dependencies still exist (deletion guard should keep them) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-ud-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-ud-test-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-ud-test-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-delete-roleassignment.yaml similarity index 74% rename from internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml rename to internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-delete-roleassignment.yaml index 65a259c22..d2ad8ee20 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/02-delete-roleassignment.yaml @@ -4,4 +4,4 @@ kind: TestStep delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment - name: roleassignment-import-dependency + name: roleassignment-create-user-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/README.md new file mode 100644 index 000000000..d74af937a --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-domain/README.md @@ -0,0 +1,15 @@ +# Create a RoleAssignment for User on Domain + +## Step 00 + +Create dependencies (Role, User, Domain) and a RoleAssignment that assigns a role to a user on a domain. + +Verify that the observed state corresponds to the spec and the role assignment exists in OpenStack. + +## Step 01 + +Delete the RoleAssignment and verify it's removed from OpenStack. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-assert.yaml new file mode 100644 index 000000000..7a56b4d4c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-assert.yaml @@ -0,0 +1,75 @@ +--- +# Assert Role is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-up-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert User is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-up-test-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Project is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-up-test-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert RoleAssignment is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-user-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Validate RoleAssignment status fields +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-user-project + ref: roleassignment +assertAll: + # Verify status.id is NOT set (role assignments use component-based identification) + - celExpr: "!has(roleassignment.status.id) || roleassignment.status.id == ''" + # Verify all component IDs are populated in status.resource + - celExpr: "roleassignment.status.resource.roleID != ''" + - celExpr: "roleassignment.status.resource.userID != ''" + - celExpr: "roleassignment.status.resource.projectID != ''" + # Verify group and domain are not set (since we used user and project) + - celExpr: "!has(roleassignment.status.resource.groupID) || roleassignment.status.resource.groupID == ''" + - celExpr: "!has(roleassignment.status.resource.domainID) || roleassignment.status.resource.domainID == ''" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-create-resource.yaml new file mode 100644 index 000000000..dbc89aed6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-create-resource.yaml @@ -0,0 +1,54 @@ +--- +# Create a test role +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-up-test-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-up-test-role +--- +# Create a test user +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-up-test-user +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-up-test-user +--- +# Create a test project +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-up-test-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-up-test-project +--- +# Create the role assignment +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-user-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-up-test-role + userRef: roleassignment-up-test-user + projectRef: roleassignment-up-test-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-secret.yaml similarity index 98% rename from internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml rename to internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-secret.yaml index 045711ee7..f0fb63e85 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/00-secret.yaml @@ -1,4 +1,3 @@ ---- apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-assert.yaml new file mode 100644 index 000000000..607c22ab4 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-assert.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-create-user-project --namespace $NAMESPACE" + skipLogOutput: true +--- +# Verify dependencies still exist (deletion guard should keep them) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-up-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-up-test-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-up-test-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-delete-roleassignment.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-delete-roleassignment.yaml new file mode 100644 index 000000000..9b20de4ef --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/01-delete-roleassignment.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-user-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-user-project/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/README.md new file mode 100644 index 000000000..3b92d0da2 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-user-project/README.md @@ -0,0 +1,15 @@ +# Create a RoleAssignment with minimum options + +## Step 00 + +Create dependencies (Role, User, Project) and a minimal RoleAssignment that assigns a role to a user on a project. + +Verify that the observed state corresponds to the spec and the role assignment exists in OpenStack. + +## Step 01 + +Delete the RoleAssignment and verify it's removed from OpenStack. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml index ce8bc6357..448d7ee63 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml @@ -1,90 +1,13 @@ --- +# Verify RoleAssignment is Progressing (waiting for dependencies) apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-dependency-no-secret + name: roleassignment-dependency status: conditions: - - type: Available - message: Waiting for Secret/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Secret/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-role -status: - conditions: - - type: Available - message: Waiting for Role/roleassignment-dependency-pending to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Role/roleassignment-dependency-pending to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-user -status: - conditions: - - type: Available - message: Waiting for User/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for User/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-group -status: - conditions: - - type: Available - message: Waiting for Group/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Group/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-project -status: - conditions: - - type: Available - message: Waiting for Project/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Project/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-domain -status: - conditions: - - type: Available - message: Waiting for Domain/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Domain/roleassignment-dependency to be created - status: "True" - reason: Progressing + - type: Available + status: "False" + - type: Progressing + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml index 9666ee6c6..412216039 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml @@ -1,98 +1,15 @@ --- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-dependency-no-role -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the resource ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-user -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency - userRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- +# Create RoleAssignment with missing dependencies apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-dependency-no-group -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency - groupRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: - roleRef: roleassignment-dependency - projectRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-domain + name: roleassignment-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 resource: - roleRef: roleassignment-dependency - domainRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-secret -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: roleassignment-dependency - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: - roleRef: roleassignment-dependency + roleRef: roleassignment-dep-role + userRef: roleassignment-dep-user + projectRef: roleassignment-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml index 045711ee7..f0fb63e85 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml @@ -1,4 +1,3 @@ ---- apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml index 2fd8a79b1..7ddf9a725 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml @@ -1,90 +1,76 @@ --- +# Verify dependencies are Available apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment +kind: Role metadata: - name: roleassignment-dependency-no-role + name: roleassignment-dep-role 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 + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment +kind: User metadata: - name: roleassignment-dependency-no-user + name: roleassignment-dep-user 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 + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment +kind: Project metadata: - name: roleassignment-dependency-no-group + name: roleassignment-dep-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 + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- +# Verify RoleAssignment is now Available apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-dependency-no-project + name: roleassignment-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 + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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 +# Verify deletion guard finalizers are set on dependencies +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-dep-role + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-dep-user + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-dep-project + ref: project +assertAll: + # Check that deletion guard finalizers are present + - celExpr: "role.metadata.finalizers.exists(f, f.startsWith('openstack.k-orc.cloud/roleassignment'))" + - celExpr: "user.metadata.finalizers.exists(f, f.startsWith('openstack.k-orc.cloud/roleassignment'))" + - celExpr: "project.metadata.finalizers.exists(f, f.startsWith('openstack.k-orc.cloud/roleassignment'))" diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml index 5e18664c5..a113632a8 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml @@ -1,71 +1,37 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic roleassignment-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true ---- +# Create the dependencies apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Role metadata: - name: roleassignment-dependency-pending + name: roleassignment-dep-role 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: + name: roleassignment-dep-role --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: - name: roleassignment-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: Group -metadata: - name: roleassignment-dependency + name: roleassignment-dep-user 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: + name: roleassignment-dep-user --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project metadata: - name: roleassignment-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: Domain -metadata: - name: roleassignment-dependency + name: roleassignment-dep-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 - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: roleassignment-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml index d91675d9d..f1c77d3b5 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml @@ -1,41 +1,29 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-dependency - ref: role - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: roleassignment-dependency - ref: user - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group - name: roleassignment-dependency - ref: group - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: roleassignment-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: roleassignment-dependency - ref: domain - - apiVersion: v1 - kind: Secret - name: roleassignment-dependency - ref: secret -assertAll: - - celExpr: "role.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in role.metadata.finalizers" - - celExpr: "user.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in user.metadata.finalizers" - - celExpr: "group.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in group.metadata.finalizers" - - celExpr: "project.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in project.metadata.finalizers" - - celExpr: "domain.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in domain.metadata.finalizers" - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in secret.metadata.finalizers" +# Verify Project still exists (deletion blocked by finalizer) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-dep-project + # deletionTimestamp should be set, but resource should still exist +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Verify RoleAssignment still Available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml index 7d2647c44..3ea65dfe7 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml @@ -1,17 +1,6 @@ --- +# Try to delete a dependency (should be blocked by finalizer) apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: - # We expect the deletion to hang due to the finalizer, so use --wait=false - - command: kubectl delete role.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete user.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete group.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete project.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete secret roleassignment-dependency --wait=false - namespaced: true + - command: kubectl delete project roleassignment-dep-project --namespace $NAMESPACE --wait=false diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml index c9d8176db..1a99c8e08 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml @@ -2,16 +2,9 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -# Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-dependency --namespace $NAMESPACE" skipLogOutput: true -- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get secret roleassignment-dependency --namespace $NAMESPACE" +# Verify Project can now be deleted (finalizer removed) +- script: "! kubectl get project roleassignment-dep-project --namespace $NAMESPACE" skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml index bd5ab1aec..cc50ec3db 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml @@ -1,22 +1,8 @@ --- +# Delete RoleAssignment first apiVersion: kuttl.dev/v1beta1 kind: TestStep delete: -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-secret -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-role -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-user -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-group -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-project -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md index 2682e773e..49d537638 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md @@ -1,21 +1,23 @@ -# Creation and deletion dependencies +# Test RoleAssignment dependency handling ## Step 00 -Create RoleAssignments referencing non-existing resources. Each RoleAssignment is dependent on other non-existing resource. Verify that the RoleAssignments are waiting for the needed resources to be created externally. +Create a RoleAssignment that references Role, User, and Project that don't exist yet. +Verify that it enters Progressing state waiting for dependencies. ## Step 01 -Create the missing dependencies and verify all the RoleAssignments are available. +Create the dependencies and verify the RoleAssignment becomes Available. ## Step 02 -Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. +Try to delete a dependency (Project) while it's still referenced by the RoleAssignment. +Verify the deletion is blocked by the finalizer. ## Step 03 -Delete the RoleAssignments and validate that all resources are gone. +Delete the RoleAssignment first, then verify dependencies can be deleted. ## Reference -https://k-orc.cloud/development/writing-tests/#dependency +https://k-orc.cloud/development/writing-tests/#dependencies diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml deleted file mode 100644 index d67606898..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency -status: - conditions: - - type: Available - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "False" - reason: Progressing - - type: Progressing - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "True" - reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml deleted file mode 100644 index cbde79d20..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml +++ /dev/null @@ -1,82 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - roleRef: roleassignment-import-dependency - userRef: roleassignment-import-dependency - groupRef: roleassignment-import-dependency - projectRef: roleassignment-import-dependency - domainRef: roleassignment-import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml deleted file mode 100644 index bf4fc3938..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-import-dependency -status: - conditions: - - type: Available - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "False" - reason: Progressing - - type: Progressing - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "True" - reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml deleted file mode 100644 index d7edf5088..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml +++ /dev/null @@ -1,84 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-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 `roleassignment-import-dependency-not-this-one` should not be picked by the import filter -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: - roleRef: roleassignment-import-dependency-not-this-one - userRef: roleassignment-import-dependency-not-this-one - groupRef: roleassignment-import-dependency-not-this-one - projectRef: roleassignment-import-dependency-not-this-one - domainRef: roleassignment-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml deleted file mode 100644 index 3c81e999a..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml +++ /dev/null @@ -1,54 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-dependency - ref: roleassignment1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-dependency-not-this-one - ref: roleassignment2 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-import-dependency - ref: role - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: roleassignment-import-dependency - ref: user - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group - name: roleassignment-import-dependency - ref: group - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: roleassignment-import-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: roleassignment-import-dependency - ref: domain -assertAll: - - celExpr: "roleassignment1.status.id != roleassignment2.status.id" - - celExpr: "roleassignment1.status.resource.roleID == role.status.id" - - celExpr: "roleassignment1.status.resource.userID == user.status.id" - - celExpr: "roleassignment1.status.resource.groupID == group.status.id" - - celExpr: "roleassignment1.status.resource.projectID == project.status.id" - - celExpr: "roleassignment1.status.resource.domainID == domain.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml deleted file mode 100644 index aa6a4b273..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml +++ /dev/null @@ -1,83 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: roleassignment-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: Group -metadata: - name: roleassignment-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: Project -metadata: - name: roleassignment-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: Domain -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-import-dependency-external - userRef: roleassignment-import-dependency-external - groupRef: roleassignment-import-dependency-external - projectRef: roleassignment-import-dependency-external - domainRef: roleassignment-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml deleted file mode 100644 index 5ae60674d..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: -- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml deleted file mode 100644 index 6779311b6..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - # We should be able to delete the import dependencies - - command: kubectl delete role.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete user.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete group.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete project.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml deleted file mode 100644 index 1b9333e8c..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: -- script: "! kubectl get roleassignment.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md deleted file mode 100644 index e42814f39..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Check dependency handling for imported RoleAssignment - -## Step 00 - -Import a RoleAssignment that references other imported resources. The referenced imported resources have no matching resources yet. -Verify the RoleAssignment is waiting for the dependency to be ready. - -## Step 01 - -Create a RoleAssignment matching the import filter, except for referenced resources, and verify that it's not being imported. - -## Step 02 - -Create the referenced resources and a RoleAssignment matching the import filters. - -Verify that the observed status on the imported RoleAssignment corresponds to the spec of the created RoleAssignment. - -## 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 RoleAssignment and validate that all resources are gone. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml deleted file mode 100644 index a0fbff00c..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml deleted file mode 100644 index a85f3b243..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml +++ /dev/null @@ -1,43 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment from "import error" test - roleRef: roleassignment-import-error - # TODO(scaffolding): add any required field ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment from "import error" test - roleRef: roleassignment-import-error - # TODO(scaffolding): add any required field diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml deleted file mode 100644 index 1f7e3a893..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml deleted file mode 100644 index 5589b99b8..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - description: RoleAssignment from "import error" test diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md deleted file mode 100644 index 26ca68ce4..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Import RoleAssignment with more than one matching resources - -## Step 00 - -Create two RoleAssignments with identical specs. - -## Step 01 - -Ensure that an imported RoleAssignment with a filter matching the resources returns an error. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml deleted file mode 100644 index fdc36fdd0..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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/roleassignment/tests/roleassignment-import/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml deleted file mode 100644 index e4d6ce579..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-external - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.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/roleassignment/tests/roleassignment-import/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml deleted file mode 100644 index 9a908403b..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: roleassignment-import-external-not-this-one - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - # TODO(scaffolding): Add fields necessary to match filter ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml deleted file mode 100644 index a991d3d80..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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 `roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment roleassignment-import-external from "roleassignment-import" test - roleRef: roleassignment-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml deleted file mode 100644 index b5b5eb744..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml +++ /dev/null @@ -1,33 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-external - ref: roleassignment1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-external-not-this-one - ref: roleassignment2 -assertAll: - - celExpr: "roleassignment1.status.id != roleassignment2.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-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: roleassignment-import-external - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml deleted file mode 100644 index a4613b0a5..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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: RoleAssignment roleassignment-import-external from "roleassignment-import" test - roleRef: roleassignment-import - # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/README.md b/internal/controllers/roleassignment/tests/roleassignment-import/README.md deleted file mode 100644 index 0d655cbb5..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Import RoleAssignment - -## Step 00 - -Import a roleassignment that matches all fields in the filter, and verify it is waiting for the external resource to be created. - -## Step 01 - -Create a roleassignment 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 roleassignment matching the filter and verify that the observed status on the imported roleassignment corresponds to the spec of the created roleassignment. -Also, confirm that it does not adopt any roleassignment whose name is a superstring of its own. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml deleted file mode 100644 index 09171f4ed..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-update - ref: roleassignment -assertAll: - - celExpr: "!has(roleassignment.status.resource.description)" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -status: - resource: - name: roleassignment-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/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml deleted file mode 100644 index dded7b069..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-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: RoleAssignment -metadata: - name: roleassignment-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: - roleRef: roleassignment-update diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.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/roleassignment/tests/roleassignment-update/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml deleted file mode 100644 index 6ac85e51b..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -status: - resource: - name: roleassignment-update-updated - description: roleassignment-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/roleassignment/tests/roleassignment-update/01-updated-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml deleted file mode 100644 index d07ade78c..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -spec: - resource: - name: roleassignment-update-updated - description: roleassignment-update-updated - # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml deleted file mode 100644 index 15536b2d3..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-update - ref: roleassignment -assertAll: - - celExpr: "!has(roleassignment.status.resource.description)" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -status: - resource: - name: roleassignment-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/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml deleted file mode 100644 index 2c6c253ff..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# 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/roleassignment/tests/roleassignment-update/README.md b/internal/controllers/roleassignment/tests/roleassignment-update/README.md deleted file mode 100644 index 8c0d2dee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Update RoleAssignment - -## Step 00 - -Create a RoleAssignment 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/roleassignment/zz_generated.adapter.go b/internal/controllers/roleassignment/zz_generated.adapter.go new file mode 100644 index 000000000..53f478a76 --- /dev/null +++ b/internal/controllers/roleassignment/zz_generated.adapter.go @@ -0,0 +1,78 @@ +// 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 roleassignment + +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.RoleAssignment + orcObjectListT = orcv1alpha1.RoleAssignmentList + resourceSpecT = orcv1alpha1.RoleAssignmentResourceSpec + filterT = orcv1alpha1.RoleAssignmentFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = roleassignmentAdapter +) + +type roleassignmentAdapter struct { + *orcv1alpha1.RoleAssignment +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.RoleAssignment +} + +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 +} diff --git a/internal/controllers/roleassignment/zz_generated.controller.go b/internal/controllers/roleassignment/zz_generated.controller.go new file mode 100644 index 000000000..469e96460 --- /dev/null +++ b/internal/controllers/roleassignment/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 roleassignment + +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 766500c8f..57088736b 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -56,6 +56,9 @@ import ( //go:generate mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt role.go > _role.go && mv _role.go role.go" +//go:generate mockgen -package mock -destination=roleassignment.go -source=../roleassignment.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleAssignmentClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt roleassignment.go > _roleassignment.go && mv _roleassignment.go roleassignment.go" + //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" diff --git a/internal/osclients/mock/roleassignment.go b/internal/osclients/mock/roleassignment.go new file mode 100644 index 000000000..fa513aba8 --- /dev/null +++ b/internal/osclients/mock/roleassignment.go @@ -0,0 +1,100 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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: ../roleassignment.go +// +// Generated by this command: +// +// mockgen -package mock -destination=roleassignment.go -source=../roleassignment.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleAssignmentClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + roles "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + gomock "go.uber.org/mock/gomock" +) + +// MockRoleAssignmentClient is a mock of RoleAssignmentClient interface. +type MockRoleAssignmentClient struct { + ctrl *gomock.Controller + recorder *MockRoleAssignmentClientMockRecorder + isgomock struct{} +} + +// MockRoleAssignmentClientMockRecorder is the mock recorder for MockRoleAssignmentClient. +type MockRoleAssignmentClientMockRecorder struct { + mock *MockRoleAssignmentClient +} + +// NewMockRoleAssignmentClient creates a new mock instance. +func NewMockRoleAssignmentClient(ctrl *gomock.Controller) *MockRoleAssignmentClient { + mock := &MockRoleAssignmentClient{ctrl: ctrl} + mock.recorder = &MockRoleAssignmentClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRoleAssignmentClient) EXPECT() *MockRoleAssignmentClientMockRecorder { + return m.recorder +} + +// AssignRole mocks base method. +func (m *MockRoleAssignmentClient) AssignRole(ctx context.Context, roleID string, opts roles.AssignOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssignRole", ctx, roleID, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// AssignRole indicates an expected call of AssignRole. +func (mr *MockRoleAssignmentClientMockRecorder) AssignRole(ctx, roleID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignRole", reflect.TypeOf((*MockRoleAssignmentClient)(nil).AssignRole), ctx, roleID, opts) +} + +// ListRoleAssignments mocks base method. +func (m *MockRoleAssignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRoleAssignments", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*roles.RoleAssignment, error]) + return ret0 +} + +// ListRoleAssignments indicates an expected call of ListRoleAssignments. +func (mr *MockRoleAssignmentClientMockRecorder) ListRoleAssignments(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRoleAssignments", reflect.TypeOf((*MockRoleAssignmentClient)(nil).ListRoleAssignments), ctx, listOpts) +} + +// UnassignRole mocks base method. +func (m *MockRoleAssignmentClient) UnassignRole(ctx context.Context, roleID string, opts roles.UnassignOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnassignRole", ctx, roleID, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnassignRole indicates an expected call of UnassignRole. +func (mr *MockRoleAssignmentClientMockRecorder) UnassignRole(ctx, roleID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnassignRole", reflect.TypeOf((*MockRoleAssignmentClient)(nil).UnassignRole), ctx, roleID, opts) +} diff --git a/internal/osclients/roleassignment.go b/internal/osclients/roleassignment.go index 62da755cd..bf83a75b2 100644 --- a/internal/osclients/roleassignment.go +++ b/internal/osclients/roleassignment.go @@ -28,16 +28,14 @@ import ( ) type RoleAssignmentClient interface { - ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] - CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) - DeleteRoleAssignment(ctx context.Context, resourceID string) error - GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) - UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) + ListRoleAssignments(ctx context.Context, listOpts roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] + AssignRole(ctx context.Context, roleID string, opts roles.AssignOpts) error + UnassignRole(ctx context.Context, roleID string, opts roles.UnassignOpts) error } type roleassignmentClient struct{ client *gophercloud.ServiceClient } -// NewRoleAssignmentClient returns a new OpenStack client. +// NewRoleAssignmentClient returns a new OpenStack Identity client for role assignments. func NewRoleAssignmentClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (RoleAssignmentClient, error) { client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ Region: providerClientOpts.RegionName, @@ -45,33 +43,25 @@ func NewRoleAssignmentClient(providerClient *gophercloud.ProviderClient, provide }) if err != nil { - return nil, fmt.Errorf("failed to create roleassignment service client: %v", err) + return nil, fmt.Errorf("failed to create role assignment service client: %v", err) } return &roleassignmentClient{client}, nil } -func (c roleassignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { - pager := roles.List(c.client, listOpts) +func (c roleassignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { + pager := roles.ListAssignments(c.client, listOpts) return func(yield func(*roles.RoleAssignment, error) bool) { _ = pager.EachPage(ctx, yieldPage(roles.ExtractRoleAssignments, yield)) } } -func (c roleassignmentClient) CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { - return roles.Create(ctx, c.client, opts).Extract() +func (c roleassignmentClient) AssignRole(ctx context.Context, roleID string, opts roles.AssignOpts) error { + return roles.Assign(ctx, c.client, roleID, opts).ExtractErr() } -func (c roleassignmentClient) DeleteRoleAssignment(ctx context.Context, resourceID string) error { - return roles.Delete(ctx, c.client, resourceID).ExtractErr() -} - -func (c roleassignmentClient) GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) { - return roles.Get(ctx, c.client, resourceID).Extract() -} - -func (c roleassignmentClient) UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { - return roles.Update(ctx, c.client, id, opts).Extract() +func (c roleassignmentClient) UnassignRole(ctx context.Context, roleID string, opts roles.UnassignOpts) error { + return roles.Unassign(ctx, c.client, roleID, opts).ExtractErr() } type roleassignmentErrorClient struct{ error } @@ -81,24 +71,16 @@ func NewRoleAssignmentErrorClient(e error) RoleAssignmentClient { return roleassignmentErrorClient{e} } -func (e roleassignmentErrorClient) ListRoleAssignments(_ context.Context, _ roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { +func (e roleassignmentErrorClient) ListRoleAssignments(_ context.Context, _ roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { return func(yield func(*roles.RoleAssignment, error) bool) { yield(nil, e.error) } } -func (e roleassignmentErrorClient) CreateRoleAssignment(_ context.Context, _ roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { - return nil, e.error -} - -func (e roleassignmentErrorClient) DeleteRoleAssignment(_ context.Context, _ string) error { +func (e roleassignmentErrorClient) AssignRole(_ context.Context, _ string, _ roles.AssignOpts) error { return e.error } -func (e roleassignmentErrorClient) GetRoleAssignment(_ context.Context, _ string) (*roles.RoleAssignment, error) { - return nil, e.error -} - -func (e roleassignmentErrorClient) UpdateRoleAssignment(_ context.Context, _ string, _ roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { - return nil, e.error +func (e roleassignmentErrorClient) UnassignRole(_ context.Context, _ string, _ roles.UnassignOpts) error { + return e.error } diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 256fa2e1d..fdf984782 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -45,6 +45,7 @@ type MockScopeFactory struct { KeyPairClient *mock.MockKeyPairClient NetworkClient *mock.MockNetworkClient RoleClient *mock.MockRoleClient + RoleAssignmentClient *mock.MockRoleAssignmentClient ServiceClient *mock.MockServiceClient UserClient *mock.MockUserClient VolumeClient *mock.MockVolumeClient @@ -65,6 +66,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { keypairClient := mock.NewMockKeyPairClient(mockCtrl) networkClient := mock.NewMockNetworkClient(mockCtrl) roleClient := mock.NewMockRoleClient(mockCtrl) + roleassignmentClient := mock.NewMockRoleAssignmentClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) userClient := mock.NewMockUserClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) @@ -82,6 +84,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { KeyPairClient: keypairClient, NetworkClient: networkClient, RoleClient: roleClient, + RoleAssignmentClient: roleassignmentClient, ServiceClient: serviceClient, UserClient: userClient, VolumeClient: volumeClient, @@ -152,6 +155,10 @@ func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) { return f.RoleClient, nil } +func (f *MockScopeFactory) NewRoleAssignmentClient() (osclients.RoleAssignmentClient, error) { + return f.RoleAssignmentClient, nil +} + func (f *MockScopeFactory) NewEndpointClient() (osclients.EndpointClient, error) { return f.EndpointClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index aadd5c5ff..c6fd90099 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -197,6 +197,10 @@ func (s *providerScope) NewRoleClient() (clients.RoleClient, error) { return clients.NewRoleClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewRoleAssignmentClient() (clients.RoleAssignmentClient, error) { + return clients.NewRoleAssignmentClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) ExtractToken() (*tokens.Token, error) { client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{}) if err != nil { diff --git a/internal/scope/scope.go b/internal/scope/scope.go index d8426fd62..63d6f649d 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -59,6 +59,7 @@ type Scope interface { NewKeyPairClient() (osclients.KeyPairClient, error) NewNetworkClient() (osclients.NetworkClient, error) NewRoleClient() (osclients.RoleClient, error) + NewRoleAssignmentClient() (osclients.RoleAssignmentClient, error) NewServiceClient() (osclients.ServiceClient, error) NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d58526493..99d71a82c 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -15,6 +15,7 @@ testDirs: - ./internal/controllers/port/tests/ - ./internal/controllers/project/tests/ - ./internal/controllers/role/tests/ +- ./internal/controllers/roleassignment/tests/ - ./internal/controllers/router/tests/ - ./internal/controllers/routerinterface/tests/ - ./internal/controllers/securitygroup/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..26462d14d --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.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" +) + +// RoleAssignmentApplyConfiguration represents a declarative configuration of the RoleAssignment type for use +// with apply. +type RoleAssignmentApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *RoleAssignmentSpecApplyConfiguration `json:"spec,omitempty"` + Status *RoleAssignmentStatusApplyConfiguration `json:"status,omitempty"` +} + +// RoleAssignment constructs a declarative configuration of the RoleAssignment type for use with +// apply. +func RoleAssignment(name, namespace string) *RoleAssignmentApplyConfiguration { + b := &RoleAssignmentApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("RoleAssignment") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractRoleAssignment extracts the applied configuration owned by fieldManager from +// roleAssignment. If no managedFields are found in roleAssignment for fieldManager, a +// RoleAssignmentApplyConfiguration 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. +// roleAssignment must be a unmodified RoleAssignment API object that was retrieved from the Kubernetes API. +// ExtractRoleAssignment 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 ExtractRoleAssignment(roleAssignment *apiv1alpha1.RoleAssignment, fieldManager string) (*RoleAssignmentApplyConfiguration, error) { + return extractRoleAssignment(roleAssignment, fieldManager, "") +} + +// ExtractRoleAssignmentStatus is the same as ExtractRoleAssignment except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractRoleAssignmentStatus(roleAssignment *apiv1alpha1.RoleAssignment, fieldManager string) (*RoleAssignmentApplyConfiguration, error) { + return extractRoleAssignment(roleAssignment, fieldManager, "status") +} + +func extractRoleAssignment(roleAssignment *apiv1alpha1.RoleAssignment, fieldManager string, subresource string) (*RoleAssignmentApplyConfiguration, error) { + b := &RoleAssignmentApplyConfiguration{} + err := managedfields.ExtractInto(roleAssignment, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignment"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(roleAssignment.Name) + b.WithNamespace(roleAssignment.Namespace) + + b.WithKind("RoleAssignment") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b RoleAssignmentApplyConfiguration) 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 *RoleAssignmentApplyConfiguration) WithKind(value string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithAPIVersion(value string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithName(value string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithGenerateName(value string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithNamespace(value string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithUID(value types.UID) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithResourceVersion(value string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithGeneration(value int64) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithCreationTimestamp(value metav1.Time) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithLabels(entries map[string]string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithAnnotations(entries map[string]string) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithFinalizers(values ...string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *RoleAssignmentApplyConfiguration) 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 *RoleAssignmentApplyConfiguration) WithSpec(value *RoleAssignmentSpecApplyConfiguration) *RoleAssignmentApplyConfiguration { + 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 *RoleAssignmentApplyConfiguration) WithStatus(value *RoleAssignmentStatusApplyConfiguration) *RoleAssignmentApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go new file mode 100644 index 000000000..5367d30da --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.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" +) + +// RoleAssignmentFilterApplyConfiguration represents a declarative configuration of the RoleAssignmentFilter type for use +// with apply. +type RoleAssignmentFilterApplyConfiguration struct { + RoleRef *apiv1alpha1.KubernetesNameRef `json:"roleRef,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` + GroupRef *apiv1alpha1.KubernetesNameRef `json:"groupRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// RoleAssignmentFilterApplyConfiguration constructs a declarative configuration of the RoleAssignmentFilter type for use with +// apply. +func RoleAssignmentFilter() *RoleAssignmentFilterApplyConfiguration { + return &RoleAssignmentFilterApplyConfiguration{} +} + +// WithRoleRef sets the RoleRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithRoleRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.RoleRef = &value + return b +} + +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.UserRef = &value + return b +} + +// WithGroupRef sets the GroupRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithGroupRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.GroupRef = &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 *RoleAssignmentFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.ProjectRef = &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 *RoleAssignmentFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go new file mode 100644 index 000000000..364d27bcf --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.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 + +// RoleAssignmentImportApplyConfiguration represents a declarative configuration of the RoleAssignmentImport type for use +// with apply. +type RoleAssignmentImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *RoleAssignmentFilterApplyConfiguration `json:"filter,omitempty"` +} + +// RoleAssignmentImportApplyConfiguration constructs a declarative configuration of the RoleAssignmentImport type for use with +// apply. +func RoleAssignmentImport() *RoleAssignmentImportApplyConfiguration { + return &RoleAssignmentImportApplyConfiguration{} +} + +// 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 *RoleAssignmentImportApplyConfiguration) WithID(value string) *RoleAssignmentImportApplyConfiguration { + 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 *RoleAssignmentImportApplyConfiguration) WithFilter(value *RoleAssignmentFilterApplyConfiguration) *RoleAssignmentImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go new file mode 100644 index 000000000..680572620 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.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" +) + +// RoleAssignmentResourceSpecApplyConfiguration represents a declarative configuration of the RoleAssignmentResourceSpec type for use +// with apply. +type RoleAssignmentResourceSpecApplyConfiguration struct { + RoleRef *apiv1alpha1.KubernetesNameRef `json:"roleRef,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` + GroupRef *apiv1alpha1.KubernetesNameRef `json:"groupRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// RoleAssignmentResourceSpecApplyConfiguration constructs a declarative configuration of the RoleAssignmentResourceSpec type for use with +// apply. +func RoleAssignmentResourceSpec() *RoleAssignmentResourceSpecApplyConfiguration { + return &RoleAssignmentResourceSpecApplyConfiguration{} +} + +// WithRoleRef sets the RoleRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithRoleRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.RoleRef = &value + return b +} + +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.UserRef = &value + return b +} + +// WithGroupRef sets the GroupRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithGroupRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.GroupRef = &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 *RoleAssignmentResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.ProjectRef = &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 *RoleAssignmentResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go new file mode 100644 index 000000000..e4e29148e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.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 + +// RoleAssignmentResourceStatusApplyConfiguration represents a declarative configuration of the RoleAssignmentResourceStatus type for use +// with apply. +type RoleAssignmentResourceStatusApplyConfiguration struct { + RoleID *string `json:"roleID,omitempty"` + UserID *string `json:"userID,omitempty"` + GroupID *string `json:"groupID,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + DomainID *string `json:"domainID,omitempty"` +} + +// RoleAssignmentResourceStatusApplyConfiguration constructs a declarative configuration of the RoleAssignmentResourceStatus type for use with +// apply. +func RoleAssignmentResourceStatus() *RoleAssignmentResourceStatusApplyConfiguration { + return &RoleAssignmentResourceStatusApplyConfiguration{} +} + +// WithRoleID sets the RoleID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithRoleID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.RoleID = &value + return b +} + +// WithUserID sets the UserID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithUserID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.UserID = &value + return b +} + +// WithGroupID sets the GroupID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithGroupID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.GroupID = &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 *RoleAssignmentResourceStatusApplyConfiguration) WithProjectID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.ProjectID = &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 *RoleAssignmentResourceStatusApplyConfiguration) WithDomainID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.DomainID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go new file mode 100644 index 000000000..df29d44b1 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.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" +) + +// RoleAssignmentSpecApplyConfiguration represents a declarative configuration of the RoleAssignmentSpec type for use +// with apply. +type RoleAssignmentSpecApplyConfiguration struct { + Import *RoleAssignmentImportApplyConfiguration `json:"import,omitempty"` + Resource *RoleAssignmentResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// RoleAssignmentSpecApplyConfiguration constructs a declarative configuration of the RoleAssignmentSpec type for use with +// apply. +func RoleAssignmentSpec() *RoleAssignmentSpecApplyConfiguration { + return &RoleAssignmentSpecApplyConfiguration{} +} + +// 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 *RoleAssignmentSpecApplyConfiguration) WithImport(value *RoleAssignmentImportApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + 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 *RoleAssignmentSpecApplyConfiguration) WithResource(value *RoleAssignmentResourceSpecApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + 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 *RoleAssignmentSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *RoleAssignmentSpecApplyConfiguration { + 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 *RoleAssignmentSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + 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 *RoleAssignmentSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go new file mode 100644 index 000000000..1bb4a0d56 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.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" +) + +// RoleAssignmentStatusApplyConfiguration represents a declarative configuration of the RoleAssignmentStatus type for use +// with apply. +type RoleAssignmentStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *RoleAssignmentResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// RoleAssignmentStatusApplyConfiguration constructs a declarative configuration of the RoleAssignmentStatus type for use with +// apply. +func RoleAssignmentStatus() *RoleAssignmentStatusApplyConfiguration { + return &RoleAssignmentStatusApplyConfiguration{} +} + +// 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 *RoleAssignmentStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *RoleAssignmentStatusApplyConfiguration { + 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 *RoleAssignmentStatusApplyConfiguration) WithID(value string) *RoleAssignmentStatusApplyConfiguration { + 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 *RoleAssignmentStatusApplyConfiguration) WithResource(value *RoleAssignmentResourceStatusApplyConfiguration) *RoleAssignmentStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index b51e2a458..5f557c2ab 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2090,6 +2090,126 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleStatus default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignment + 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.RoleAssignmentSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentFilter + map: + fields: + - name: domainRef + type: + scalar: string + - name: groupRef + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: roleRef + type: + scalar: string + - name: userRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceSpec + map: + fields: + - name: domainRef + type: + scalar: string + - name: groupRef + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: roleRef + type: + scalar: string + - name: userRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceStatus + map: + fields: + - name: domainID + type: + scalar: string + - name: groupID + type: + scalar: string + - name: projectID + type: + scalar: string + - name: roleID + type: + scalar: string + - name: userID + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentSpec + 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.RoleAssignmentImport + - 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.RoleAssignmentResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentStatus + 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.RoleAssignmentResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleFilter map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 73e66b28b..d30735a7c 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -266,6 +266,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ProviderPropertiesStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Role"): return &apiv1alpha1.RoleApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignment"): + return &apiv1alpha1.RoleAssignmentApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentFilter"): + return &apiv1alpha1.RoleAssignmentFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentImport"): + return &apiv1alpha1.RoleAssignmentImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentResourceSpec"): + return &apiv1alpha1.RoleAssignmentResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentResourceStatus"): + return &apiv1alpha1.RoleAssignmentResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentSpec"): + return &apiv1alpha1.RoleAssignmentSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentStatus"): + return &apiv1alpha1.RoleAssignmentStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("RoleFilter"): return &apiv1alpha1.RoleFilterApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("RoleImport"): 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 a145cab6f..af2dbd9e0 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -41,6 +41,7 @@ type OpenstackV1alpha1Interface interface { PortsGetter ProjectsGetter RolesGetter + RoleAssignmentsGetter RoutersGetter RouterInterfacesGetter SecurityGroupsGetter @@ -111,6 +112,10 @@ func (c *OpenstackV1alpha1Client) Roles(namespace string) RoleInterface { return newRoles(c, namespace) } +func (c *OpenstackV1alpha1Client) RoleAssignments(namespace string) RoleAssignmentInterface { + return newRoleAssignments(c, namespace) +} + func (c *OpenstackV1alpha1Client) Routers(namespace string) RouterInterface { return newRouters(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 87bc2b39d..2b3498411 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 @@ -80,6 +80,10 @@ func (c *FakeOpenstackV1alpha1) Roles(namespace string) v1alpha1.RoleInterface { return newFakeRoles(c, namespace) } +func (c *FakeOpenstackV1alpha1) RoleAssignments(namespace string) v1alpha1.RoleAssignmentInterface { + return newFakeRoleAssignments(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Routers(namespace string) v1alpha1.RouterInterface { return newFakeRouters(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go new file mode 100644 index 000000000..05bbae41c --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.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" +) + +// fakeRoleAssignments implements RoleAssignmentInterface +type fakeRoleAssignments struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.RoleAssignment, *v1alpha1.RoleAssignmentList, *apiv1alpha1.RoleAssignmentApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeRoleAssignments(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.RoleAssignmentInterface { + return &fakeRoleAssignments{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.RoleAssignment, *v1alpha1.RoleAssignmentList, *apiv1alpha1.RoleAssignmentApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("roleassignments"), + v1alpha1.SchemeGroupVersion.WithKind("RoleAssignment"), + func() *v1alpha1.RoleAssignment { return &v1alpha1.RoleAssignment{} }, + func() *v1alpha1.RoleAssignmentList { return &v1alpha1.RoleAssignmentList{} }, + func(dst, src *v1alpha1.RoleAssignmentList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.RoleAssignmentList) []*v1alpha1.RoleAssignment { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.RoleAssignmentList, items []*v1alpha1.RoleAssignment) { + 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 de60388f2..7d409e9d8 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -44,6 +44,8 @@ type ProjectExpansion interface{} type RoleExpansion interface{} +type RoleAssignmentExpansion interface{} + type RouterExpansion interface{} type RouterInterfaceExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..37d5f5a96 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.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" +) + +// RoleAssignmentsGetter has a method to return a RoleAssignmentInterface. +// A group's client should implement this interface. +type RoleAssignmentsGetter interface { + RoleAssignments(namespace string) RoleAssignmentInterface +} + +// RoleAssignmentInterface has methods to work with RoleAssignment resources. +type RoleAssignmentInterface interface { + Create(ctx context.Context, roleAssignment *apiv1alpha1.RoleAssignment, opts v1.CreateOptions) (*apiv1alpha1.RoleAssignment, error) + Update(ctx context.Context, roleAssignment *apiv1alpha1.RoleAssignment, opts v1.UpdateOptions) (*apiv1alpha1.RoleAssignment, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, roleAssignment *apiv1alpha1.RoleAssignment, opts v1.UpdateOptions) (*apiv1alpha1.RoleAssignment, 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.RoleAssignment, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.RoleAssignmentList, 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.RoleAssignment, err error) + Apply(ctx context.Context, roleAssignment *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.RoleAssignment, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, roleAssignment *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.RoleAssignment, err error) + RoleAssignmentExpansion +} + +// roleAssignments implements RoleAssignmentInterface +type roleAssignments struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.RoleAssignment, *apiv1alpha1.RoleAssignmentList, *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration] +} + +// newRoleAssignments returns a RoleAssignments +func newRoleAssignments(c *OpenstackV1alpha1Client, namespace string) *roleAssignments { + return &roleAssignments{ + gentype.NewClientWithListAndApply[*apiv1alpha1.RoleAssignment, *apiv1alpha1.RoleAssignmentList, *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration]( + "roleassignments", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.RoleAssignment { return &apiv1alpha1.RoleAssignment{} }, + func() *apiv1alpha1.RoleAssignmentList { return &apiv1alpha1.RoleAssignmentList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index b9b415243..0db92472f 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -50,6 +50,8 @@ type Interface interface { Projects() ProjectInformer // Roles returns a RoleInformer. Roles() RoleInformer + // RoleAssignments returns a RoleAssignmentInformer. + RoleAssignments() RoleAssignmentInformer // Routers returns a RouterInformer. Routers() RouterInformer // RouterInterfaces returns a RouterInterfaceInformer. @@ -150,6 +152,11 @@ func (v *version) Roles() RoleInformer { return &roleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// RoleAssignments returns a RoleAssignmentInformer. +func (v *version) RoleAssignments() RoleAssignmentInformer { + return &roleAssignmentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Routers returns a RouterInformer. func (v *version) Routers() RouterInformer { return &routerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go b/pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..d34221cf4 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.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" +) + +// RoleAssignmentInformer provides access to a shared informer and lister for +// RoleAssignments. +type RoleAssignmentInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.RoleAssignmentLister +} + +type roleAssignmentInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewRoleAssignmentInformer constructs a new informer for RoleAssignment 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 NewRoleAssignmentInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRoleAssignmentInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredRoleAssignmentInformer constructs a new informer for RoleAssignment 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 NewFilteredRoleAssignmentInformer(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().RoleAssignments(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.RoleAssignment{}, + resyncPeriod, + indexers, + ) +} + +func (f *roleAssignmentInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRoleAssignmentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *roleAssignmentInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.RoleAssignment{}, f.defaultInformer) +} + +func (f *roleAssignmentInformer) Lister() apiv1alpha1.RoleAssignmentLister { + return apiv1alpha1.NewRoleAssignmentLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 99f589164..a6627b89c 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -79,6 +79,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Projects().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("roles"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Roles().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("roleassignments"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().RoleAssignments().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("routers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Routers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("routerinterfaces"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 98590bb01..77fd9e47b 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -122,6 +122,14 @@ type RoleListerExpansion interface{} // RoleNamespaceLister. type RoleNamespaceListerExpansion interface{} +// RoleAssignmentListerExpansion allows custom methods to be added to +// RoleAssignmentLister. +type RoleAssignmentListerExpansion interface{} + +// RoleAssignmentNamespaceListerExpansion allows custom methods to be added to +// RoleAssignmentNamespaceLister. +type RoleAssignmentNamespaceListerExpansion interface{} + // RouterListerExpansion allows custom methods to be added to // RouterLister. type RouterListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/roleassignment.go b/pkg/clients/listers/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..37c6c7b8d --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/roleassignment.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" +) + +// RoleAssignmentLister helps list RoleAssignments. +// All objects returned here must be treated as read-only. +type RoleAssignmentLister interface { + // List lists all RoleAssignments in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.RoleAssignment, err error) + // RoleAssignments returns an object that can list and get RoleAssignments. + RoleAssignments(namespace string) RoleAssignmentNamespaceLister + RoleAssignmentListerExpansion +} + +// roleAssignmentLister implements the RoleAssignmentLister interface. +type roleAssignmentLister struct { + listers.ResourceIndexer[*apiv1alpha1.RoleAssignment] +} + +// NewRoleAssignmentLister returns a new RoleAssignmentLister. +func NewRoleAssignmentLister(indexer cache.Indexer) RoleAssignmentLister { + return &roleAssignmentLister{listers.New[*apiv1alpha1.RoleAssignment](indexer, apiv1alpha1.Resource("roleassignment"))} +} + +// RoleAssignments returns an object that can list and get RoleAssignments. +func (s *roleAssignmentLister) RoleAssignments(namespace string) RoleAssignmentNamespaceLister { + return roleAssignmentNamespaceLister{listers.NewNamespaced[*apiv1alpha1.RoleAssignment](s.ResourceIndexer, namespace)} +} + +// RoleAssignmentNamespaceLister helps list and get RoleAssignments. +// All objects returned here must be treated as read-only. +type RoleAssignmentNamespaceLister interface { + // List lists all RoleAssignments in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.RoleAssignment, err error) + // Get retrieves the RoleAssignment from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.RoleAssignment, error) + RoleAssignmentNamespaceListerExpansion +} + +// roleAssignmentNamespaceLister implements the RoleAssignmentNamespaceLister +// interface. +type roleAssignmentNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.RoleAssignment] +} diff --git a/test/apivalidations/roleassignment_test.go b/test/apivalidations/roleassignment_test.go index 64efff717..58a3b4b59 100644 --- a/test/apivalidations/roleassignment_test.go +++ b/test/apivalidations/roleassignment_test.go @@ -42,7 +42,9 @@ func roleassignmentStub(namespace *corev1.Namespace) *orcv1alpha1.RoleAssignment func testRoleAssignmentResource() *applyconfigv1alpha1.RoleAssignmentResourceSpecApplyConfiguration { return applyconfigv1alpha1.RoleAssignmentResourceSpec(). - WithRoleRef("role") + WithRoleRef("role"). + WithUserRef("user"). + WithProjectRef("project") } func baseRoleAssignmentPatch(obj client.Object) *applyconfigv1alpha1.RoleAssignmentApplyConfiguration { @@ -79,7 +81,7 @@ var _ = Describe("ORC RoleAssignment API validations", func() { p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter())) }, applyValidFilter: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { - p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithName("foo"))) + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithRoleRef("admin"))) }, applyManaged: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) @@ -105,64 +107,21 @@ var _ = Describe("ORC RoleAssignment API validations", func() { Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) }) - It("should have immutable roleRef", func(ctx context.Context) { + It("should have immutable RoleAssignmentResourceSpec", func(ctx context.Context) { obj := roleassignmentStub(namespace) patch := baseRoleAssignmentPatch(obj) - patch.Spec.WithResource(testRoleAssignmentResource(). - WithRoleRef("role-a")) + patch.Spec.WithResource(applyconfigv1alpha1.RoleAssignmentResourceSpec(). + WithRoleRef("role"). + WithUserRef("user"). + WithProjectRef("project")) Expect(applyObj(ctx, obj, patch)).To(Succeed()) - patch.Spec.WithResource(testRoleAssignmentResource(). - WithRoleRef("role-b")) - Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("roleRef is immutable"))) - }) - - It("should have immutable userRef", func(ctx context.Context) { - obj := roleassignmentStub(namespace) - patch := baseRoleAssignmentPatch(obj) - patch.Spec.WithResource(testRoleAssignmentResource(). - WithUserRef("user-a")) - Expect(applyObj(ctx, obj, patch)).To(Succeed()) - - patch.Spec.WithResource(testRoleAssignmentResource(). - WithUserRef("user-b")) - Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("userRef is immutable"))) - }) - - It("should have immutable groupRef", func(ctx context.Context) { - obj := roleassignmentStub(namespace) - patch := baseRoleAssignmentPatch(obj) - patch.Spec.WithResource(testRoleAssignmentResource(). - WithGroupRef("group-a")) - Expect(applyObj(ctx, obj, patch)).To(Succeed()) - - patch.Spec.WithResource(testRoleAssignmentResource(). - WithGroupRef("group-b")) - Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("groupRef is immutable"))) - }) - - It("should have immutable projectRef", func(ctx context.Context) { - obj := roleassignmentStub(namespace) - patch := baseRoleAssignmentPatch(obj) - patch.Spec.WithResource(testRoleAssignmentResource(). - WithProjectRef("project-a")) - Expect(applyObj(ctx, obj, patch)).To(Succeed()) - - patch.Spec.WithResource(testRoleAssignmentResource(). - WithProjectRef("project-b")) - Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) - }) - - It("should have immutable domainRef", func(ctx context.Context) { - obj := roleassignmentStub(namespace) - patch := baseRoleAssignmentPatch(obj) - patch.Spec.WithResource(testRoleAssignmentResource(). - WithDomainRef("domain-a")) - Expect(applyObj(ctx, obj, patch)).To(Succeed()) - - patch.Spec.WithResource(testRoleAssignmentResource(). - WithDomainRef("domain-b")) - Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + // Try to change any field - should fail because entire spec is immutable + patch.Spec.WithResource(applyconfigv1alpha1.RoleAssignmentResourceSpec(). + WithRoleRef("role"). + WithUserRef("user-changed"). + WithProjectRef("project")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("RoleAssignmentResourceSpec is immutable"))) }) // TODO(scaffolding): Add more resource-specific validation tests. diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 756e1f09e..fa2bc8073 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -23,6 +23,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Port](#port) - [Project](#project) - [Role](#role) +- [RoleAssignment](#roleassignment) - [Router](#router) - [RouterInterface](#routerinterface) - [SecurityGroup](#securitygroup) @@ -513,6 +514,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) - [SecurityGroupSpec](#securitygroupspec) @@ -2157,6 +2159,8 @@ _Appears in:_ - [PortResourceSpec](#portresourcespec) - [ProjectFilter](#projectfilter) - [ProjectResourceSpec](#projectresourcespec) +- [RoleAssignmentFilter](#roleassignmentfilter) +- [RoleAssignmentResourceSpec](#roleassignmentresourcespec) - [RoleFilter](#rolefilter) - [RoleResourceSpec](#roleresourcespec) - [RouterFilter](#routerfilter) @@ -2232,6 +2236,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) - [SecurityGroupSpec](#securitygroupspec) @@ -2271,6 +2276,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) - [SecurityGroupSpec](#securitygroupspec) @@ -3068,6 +3074,149 @@ Role is the Schema for an ORC resource. | `status` _[RoleStatus](#rolestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| +#### RoleAssignment + + + +RoleAssignment is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `RoleAssignment` | | | +| `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` _[RoleAssignmentSpec](#roleassignmentspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[RoleAssignmentStatus](#roleassignmentstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### RoleAssignmentFilter + + + +RoleAssignmentFilter defines import filter criteria for existing role assignments. + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [RoleAssignmentImport](#roleassignmentimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `roleRef` _[KubernetesNameRef](#kubernetesnameref)_ | roleRef filters by the referenced Role. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef filters by the referenced User. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `groupRef` _[KubernetesNameRef](#kubernetesnameref)_ | groupRef filters by the referenced Group. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef filters by the referenced Project scope. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef filters by the referenced Domain scope. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### RoleAssignmentImport + + + +RoleAssignmentImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [RoleAssignmentSpec](#roleassignmentspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
MaxLength: 36
Optional: \{\}
| +| `filter` _[RoleAssignmentFilter](#roleassignmentfilter)_ | 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: \{\}
| + + +#### RoleAssignmentResourceSpec + + + +RoleAssignmentResourceSpec defines the desired role assignment. +A role assignment grants a role to a user or group on a project or domain. +Role assignments are immutable once created and identified by the combination +of (role, actor, scope) rather than a separate ID. + + + +_Appears in:_ +- [RoleAssignmentSpec](#roleassignmentspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `roleRef` _[KubernetesNameRef](#kubernetesnameref)_ | roleRef references the Role being assigned. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef references the User receiving the role assignment.
Exactly one of userRef or groupRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `groupRef` _[KubernetesNameRef](#kubernetesnameref)_ | groupRef references the Group receiving the role assignment.
Exactly one of userRef or groupRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef references the Project scope for the assignment.
Exactly one of projectRef or domainRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef references the Domain scope for the assignment.
Exactly one of projectRef or domainRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### RoleAssignmentResourceStatus + + + +RoleAssignmentResourceStatus represents the observed state of the role assignment. +Note: Role assignments do not have a unique ID in OpenStack - they are identified +by the combination of role, actor (user/group), and scope (project/domain). + + + +_Appears in:_ +- [RoleAssignmentStatus](#roleassignmentstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `roleID` _string_ | roleID is the OpenStack ID of the assigned role. | | MaxLength: 1024
Optional: \{\}
| +| `userID` _string_ | userID is the OpenStack ID of the user (if actorType is User). | | MaxLength: 1024
Optional: \{\}
| +| `groupID` _string_ | groupID is the OpenStack ID of the group (if actorType is Group). | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the OpenStack ID of the project scope (if scopeType is Project). | | MaxLength: 1024
Optional: \{\}
| +| `domainID` _string_ | domainID is the OpenStack ID of the domain scope (if scopeType is Domain). | | MaxLength: 1024
Optional: \{\}
| + + +#### RoleAssignmentSpec + + + +RoleAssignmentSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [RoleAssignment](#roleassignment) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[RoleAssignmentImport](#roleassignmentimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[RoleAssignmentResourceSpec](#roleassignmentresourcespec)_ | 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: \{\}
| + + +#### RoleAssignmentStatus + + + +RoleAssignmentStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [RoleAssignment](#roleassignment) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| +| `resource` _[RoleAssignmentResourceStatus](#roleassignmentresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| + + #### RoleFilter From 6d4dc02d490b901df01774e39c929ed260f496c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 20:29:39 +0000 Subject: [PATCH 189/237] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 5 updates Bumps the all-go-mod-patch-and-minor group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) | `2.28.3` | `2.29.0` | | [github.com/onsi/gomega](https://github.com/onsi/gomega) | `1.40.0` | `1.41.0` | | [k8s.io/api](https://github.com/kubernetes/api) | `0.34.7` | `0.34.8` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.34.7` | `0.34.8` | | [k8s.io/code-generator](https://github.com/kubernetes/code-generator) | `0.34.7` | `0.34.8` | Updates `github.com/onsi/ginkgo/v2` from 2.28.3 to 2.29.0 - [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.28.3...v2.29.0) Updates `github.com/onsi/gomega` from 1.40.0 to 1.41.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.40.0...v1.41.0) Updates `k8s.io/api` from 0.34.7 to 0.34.8 - [Commits](https://github.com/kubernetes/api/compare/v0.34.7...v0.34.8) Updates `k8s.io/client-go` from 0.34.7 to 0.34.8 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.7...v0.34.8) Updates `k8s.io/code-generator` from 0.34.7 to 0.34.8 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.7...v0.34.8) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.29.0 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.41.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.8 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.8 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.8 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 7fec8d66a..8839cbb64 100644 --- a/go.mod +++ b/go.mod @@ -8,15 +8,15 @@ require ( github.com/google/go-cmp v0.7.0 github.com/gophercloud/gophercloud/v2 v2.12.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.28.3 - github.com/onsi/gomega v1.40.0 + github.com/onsi/ginkgo/v2 v2.29.0 + github.com/onsi/gomega v1.41.0 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 golang.org/x/text v0.37.0 - k8s.io/api v0.34.7 + k8s.io/api v0.34.8 k8s.io/apimachinery v0.34.8 - k8s.io/client-go v0.34.7 - k8s.io/code-generator v0.34.7 + k8s.io/client-go v0.34.8 + k8s.io/code-generator v0.34.8 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 04d8022be..538dfc97f 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.28.3 h1:4JvMdwtFU0imd8fHx25OJXoDMRexnf8v5NHKYSTTji4= -github.com/onsi/ginkgo/v2 v2.28.3/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= -github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= +github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= 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= @@ -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.7 h1:186BQYhLYaaU0jhC4/uC3s4cQ+YSk0BlazRnEldXiqY= -k8s.io/api v0.34.7/go.mod h1:2qet0m0IfHSwXp+RLnunTWADUjy6qaXDa4PdeUnTJ9s= +k8s.io/api v0.34.8 h1:PATray7Ixf5fzuvW6WTrLGqWpaeSUlV3NqygBu10YC0= +k8s.io/api v0.34.8/go.mod h1:kkv5KLONEBxemk51GDsqR0TaBig60bVriYyDj9v3gg8= 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.8 h1:Vjyd/TdakksL0g7ikxyaAk3s3qgqRRYz9vj0AudwNzc= k8s.io/apimachinery v0.34.8/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= 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.7 h1:zxksYcHi4DMv6HfjMbHTfcV3jYZh7BY3yH18maoRxsA= -k8s.io/client-go v0.34.7/go.mod h1:GS3K60t9FI20+l3cvB/EfDXyD+JoKr3suIjSGISQLDc= -k8s.io/code-generator v0.34.7 h1:JSFAiGIcMnDLdUilaP3ZSXshHpPH1Elogw6wHpxeNHI= -k8s.io/code-generator v0.34.7/go.mod h1:GbTJ/TjHYE2WWDgV+9q1VTI4UkU5wGLnD2koZW0mkPY= +k8s.io/client-go v0.34.8 h1:/4Ar6TX6V/vwhFQDeTMySO0+OBbslk7inkEdOQdR66A= +k8s.io/client-go v0.34.8/go.mod h1:VVLUD4GP16X+ePPZ3YpCyxn1rhaf1lczr/HCI0e40h8= +k8s.io/code-generator v0.34.8 h1:J2+jOASoj+Y0cDdypMAIiD3/bKqpfteRx8rLsYdnMCk= +k8s.io/code-generator v0.34.8/go.mod h1:q+hSPDPvkqknwUcUX5TtuGqI6GoRFSmN4PWLcHql/i0= 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 c21b505c1ffd847b2deac8de6f1601bb59caf47e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 20:30:20 +0000 Subject: [PATCH 190/237] :seedling:(deps): Bump zizmorcore/zizmor-action Bumps the all-github-actions group with 1 update: [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action). Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.6 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/b1d7e1fb5de872772f31590499237e7cce841e8e...5f14fd08f7cf1cb1609c1e344975f152c7ee938d) --- updated-dependencies: - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/zizmor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index f10734b40..5d25c869f 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -25,4 +25,4 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # tag=v0.5.3 + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # tag=v0.5.6 From bb0239622109860e12687c18824cca7b69495939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 21 May 2026 17:26:46 +0200 Subject: [PATCH 191/237] EOL release-1.0 branch Remove release-1.0 Dependabot configuration, weekly security scan, and backport job. Mark v1.x as EOL in the README supported resources matrix and as unsupported in SECURITY.md. --- .github/dependabot.yml | 41 -------------- .github/labels.yaml | 3 - .github/workflows/backport.yaml | 62 --------------------- .github/workflows/weekly-security-scan.yaml | 2 +- README.md | 2 +- SECURITY.md | 2 +- 6 files changed, 3 insertions(+), 109 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7289db69c..42418aa7e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -43,47 +43,6 @@ updates: - dependency-name: "k8s.io/*" update-types: ["version-update:semver-major", "version-update:semver-minor"] ## main branch config ends here -## release-1.0 branch config starts here -# github-actions -- directory: "/" - package-ecosystem: "github-actions" - schedule: - interval: "weekly" - day: "monday" - cooldown: - default-days: 7 - target-branch: release-1.0 - groups: - all-github-actions: - patterns: [ "*" ] - commit-message: - prefix: ":seedling:" - include: scope -# Go directories -- directories: - - "/" - package-ecosystem: "gomod" - schedule: - interval: "weekly" - day: "monday" - cooldown: - default-days: 7 - target-branch: release-1.0 - groups: - all-go-mod-patch-and-minor: - patterns: [ "*" ] - update-types: [ "patch", "minor" ] - commit-message: - prefix: ":seedling:" - include: scope - ignore: - # Ignore controller-runtime major and minor bumps as its upgraded manually. - - dependency-name: "sigs.k8s.io/controller-runtime" - update-types: ["version-update:semver-major", "version-update:semver-minor"] - # Ignore k8s major and minor bumps and its transitives modules - - dependency-name: "k8s.io/*" - update-types: ["version-update:semver-major", "version-update:semver-minor"] -## release-1.0 branch config ends here ## release-2.0 branch config starts here # github-actions - directory: "/" diff --git a/.github/labels.yaml b/.github/labels.yaml index 7106d0e66..6552f0931 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -1,6 +1,3 @@ -- color: '30ABB9' - description: This PR will be backported to v1.0 - name: backport-v1.0 - color: '30ABB9' description: This PR targets v1.0 name: v1.0 diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index db1fbc855..25a5ed2e5 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -9,68 +9,6 @@ on: permissions: {} jobs: - backport_v1_0: - name: "Backport to v1.0" - permissions: - contents: read - pull-requests: write - # Only react to merged PRs for security reasons. - # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. - if: > - github.event.pull_request.merged - && ( - github.event.action == 'closed' - && contains(github.event.pull_request.labels.*.name, 'backport-v1.0') - || ( - github.event.action == 'labeled' - && contains(github.event.label.name, 'backport-v1.0') - ) - ) - runs-on: ubuntu-latest - steps: - - name: Generate a token from the orc-backport-bot github-app - id: generate_token - uses: getsentry/action-github-app-token@5c1e90706fe007857338ac1bfbd7a4177db2f789 # tag=v4.0.0 - with: - 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: > - contains(github.event.pull_request.labels.*.name, 'semver:patch') - || contains(github.event.label.name, 'semver:patch') - uses: kiegroup/git-backporting@08da0b07ef2330d189f6074ec8db736b3aa9f465 # tag=v4.9.1 - with: - target-branch: release-1.0 - pull-request: ${{ github.event.pull_request.url }} - auth: ${{ steps.generate_token.outputs.token }} - no-squash: true - strategy-option: find-renames - - - name: Report failure - if: failure() - run: gh issue comment "$NUMBER" --body "$BODY" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} - BODY: > - Failed to backport PR to `release-1.0` branch. See [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. - - - name: Report an error if backport unsupported labels - if: > - contains(github.event.pull_request.labels.*.name, 'semver:major') - || contains(github.event.pull_request.labels.*.name, 'semver:minor') - || contains(github.event.label.name, 'semver:major') - || contains(github.event.label.name, 'semver:minor') - run: gh pr comment "$NUMBER" --body "$BODY" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.pull_request.number }} - BODY: > - Labels `semver:major` and `semver:minor` block backports to the branch `release-1.0`. - backport_v2_0: name: "Backport to v2.0" permissions: diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 44c7239b9..703f9332d 100644 --- a/.github/workflows/weekly-security-scan.yaml +++ b/.github/workflows/weekly-security-scan.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - branch: [main, release-1.0, release-2.0] + branch: [main, release-2.0] name: Trivy runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index c3b527d92..f2dac4c5b 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ kubectl delete -f $ORC_RELEASE ## Supported OpenStack resources -| **controller** | **1.x** | **2.x** | **main** | +| **controller** | **1.x (EOL)** | **2.x** | **main** | |:---------------------------:|:-------:|:-------:|:--------:| | addressscope | | ✔ | ✔ | | application credential | | ◐ | ◐ | diff --git a/SECURITY.md b/SECURITY.md index 936f73a2d..e8a9e7210 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,7 +5,7 @@ | Version | Supported | | ------- | ------------------ | | < 1.0 | :x: | -| 1.x | :white_check_mark: | +| 1.x | :x: | | 2.x | :white_check_mark: | ## Reporting a Vulnerability From fa035e10198159158e7de93701a5c1dd06195b43 Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Mon, 23 Feb 2026 16:45:48 +0000 Subject: [PATCH 192/237] Scaffolding tool used to create boilerplate for Share Network Controller go run ./cmd/scaffold-controller -interactive=false \ -kind=ShareNetwork \ -gophercloud-client=NewSharedFilesystemV2 \ -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks \ -gophercloud-type=ShareNetwork \ -openstack-json-object=share_network \ -optional-create-dependency=Network \ -optional-create-dependency=Subnet Signed-off-by: Daniel Lawton --- api/v1alpha1/sharenetwork_types.go | 94 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 75 +++++ cmd/models-schema/zz_generated.openapi.go | 112 +++++++ config/rbac/role.yaml | 2 + .../openstack_v1alpha1_sharenetwork.yaml | 14 + internal/controllers/sharenetwork/actuator.go | 281 ++++++++++++++++++ .../controllers/sharenetwork/actuator_test.go | 119 ++++++++ .../controllers/sharenetwork/controller.go | 114 +++++++ internal/controllers/sharenetwork/status.go | 77 +++++ .../sharenetwork-create-full/00-assert.yaml | 38 +++ .../00-create-resource.yaml | 43 +++ .../sharenetwork-create-full/00-secret.yaml | 6 + .../tests/sharenetwork-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 + .../sharenetwork-create-minimal/README.md | 15 + .../sharenetwork-dependency/00-assert.yaml | 45 +++ .../00-create-resources-missing-deps.yaml | 40 +++ .../sharenetwork-dependency/00-secret.yaml | 6 + .../sharenetwork-dependency/01-assert.yaml | 45 +++ .../01-create-dependencies.yaml | 32 ++ .../sharenetwork-dependency/02-assert.yaml | 23 ++ .../02-delete-dependencies.yaml | 11 + .../sharenetwork-dependency/03-assert.yaml | 11 + .../03-delete-resources.yaml | 13 + .../tests/sharenetwork-dependency/README.md | 21 ++ .../sharenetwork-import-error/00-assert.yaml | 30 ++ .../00-create-resources.yaml | 28 ++ .../sharenetwork-import-error/00-secret.yaml | 6 + .../sharenetwork-import-error/01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../tests/sharenetwork-import-error/README.md | 13 + .../tests/sharenetwork-import/00-assert.yaml | 15 + .../00-import-resource.yaml | 15 + .../tests/sharenetwork-import/00-secret.yaml | 6 + .../tests/sharenetwork-import/01-assert.yaml | 34 +++ .../01-create-trap-resource.yaml | 17 ++ .../tests/sharenetwork-import/02-assert.yaml | 33 ++ .../02-create-resource.yaml | 14 + .../tests/sharenetwork-import/README.md | 18 ++ .../tests/sharenetwork-update/00-assert.yaml | 26 ++ .../00-minimal-resource.yaml | 14 + .../tests/sharenetwork-update/00-secret.yaml | 6 + .../tests/sharenetwork-update/01-assert.yaml | 17 ++ .../01-updated-resource.yaml | 10 + .../tests/sharenetwork-update/02-assert.yaml | 26 ++ .../02-reverted-resource.yaml | 7 + .../tests/sharenetwork-update/README.md | 17 ++ internal/osclients/sharenetwork.go | 104 +++++++ website/docs/crd-reference.md | 9 + 53 files changed, 1806 insertions(+) create mode 100644 api/v1alpha1/sharenetwork_types.go create mode 100644 config/samples/openstack_v1alpha1_sharenetwork.yaml create mode 100644 internal/controllers/sharenetwork/actuator.go create mode 100644 internal/controllers/sharenetwork/actuator_test.go create mode 100644 internal/controllers/sharenetwork/controller.go create mode 100644 internal/controllers/sharenetwork/status.go create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-full/README.md create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/01-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/README.md create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/02-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-dependency/README.md create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import-error/01-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import-error/01-import-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import-error/README.md create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/00-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-import/README.md create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/00-secret.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/01-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/02-reverted-resource.yaml create mode 100644 internal/controllers/sharenetwork/tests/sharenetwork-update/README.md create mode 100644 internal/osclients/sharenetwork.go diff --git a/api/v1alpha1/sharenetwork_types.go b/api/v1alpha1/sharenetwork_types.go new file mode 100644 index 000000000..f0a5a813a --- /dev/null +++ b/api/v1alpha1/sharenetwork_types.go @@ -0,0 +1,94 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 + +// ShareNetworkResourceSpec contains the desired state of the resource. +type ShareNetworkResourceSpec 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"` + + // networkRef is a reference to the ORC Network which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="networkRef is immutable" + NetworkRef *KubernetesNameRef `json:"networkRef,omitempty"` + + // subnetRef is a reference to the ORC Subnet which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="subnetRef is immutable" + SubnetRef *KubernetesNameRef `json:"subnetRef,omitempty"` + + // 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/sharedfilesystems/v2/sharenetworks + // + // 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"` +} + +// ShareNetworkFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type ShareNetworkFilter struct { + // name of the existing resource + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description of the existing resource + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ListOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks +} + +// ShareNetworkResourceStatus represents the observed state of the resource. +type ShareNetworkResourceStatus 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"` + + // networkID is the ID of the Network to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + NetworkID string `json:"networkID,omitempty"` + + // subnetID is the ID of the Subnet to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + SubnetID string `json:"subnetID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ShareNetwork structure from + // github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 09694efa4..b3d1b3950 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5599,6 +5599,81 @@ func (in *ServiceStatus) DeepCopy() *ServiceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkFilter) DeepCopyInto(out *ShareNetworkFilter) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(OpenStackName) + **out = **in + } + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkFilter. +func (in *ShareNetworkFilter) DeepCopy() *ShareNetworkFilter { + if in == nil { + return nil + } + out := new(ShareNetworkFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkResourceSpec) DeepCopyInto(out *ShareNetworkResourceSpec) { + *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.NetworkRef != nil { + in, out := &in.NetworkRef, &out.NetworkRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.SubnetRef != nil { + in, out := &in.SubnetRef, &out.SubnetRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkResourceSpec. +func (in *ShareNetworkResourceSpec) DeepCopy() *ShareNetworkResourceSpec { + if in == nil { + return nil + } + out := new(ShareNetworkResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkResourceStatus) DeepCopyInto(out *ShareNetworkResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkResourceStatus. +func (in *ShareNetworkResourceStatus) DeepCopy() *ShareNetworkResourceStatus { + if in == nil { + return nil + } + out := new(ShareNetworkResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Subnet) DeepCopyInto(out *Subnet) { *out = *in diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3e7cff7c6..b5e5818bc 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -223,6 +223,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Subnet": schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), @@ -10567,6 +10570,115 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref comm } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkFilter defines an existing resource by its properties", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description of the existing resource", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkResourceSpec 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: "", + }, + }, + "networkRef": { + SchemaProps: spec.SchemaProps{ + Description: "networkRef is a reference to the ORC Network which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + "subnetRef": { + SchemaProps: spec.SchemaProps{ + Description: "subnetRef is a reference to the ORC Subnet which this resource is associated with.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkResourceStatus 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: "", + }, + }, + "networkID": { + SchemaProps: spec.SchemaProps{ + Description: "networkID is the ID of the Network to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + "subnetID": { + SchemaProps: spec.SchemaProps{ + Description: "subnetID is the ID of the Subnet to which the resource is associated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3b67eb9fa..4991cff67 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -36,6 +36,7 @@ rules: - servergroups - servers - services + - sharenetworks - subnets - trunks - users @@ -71,6 +72,7 @@ rules: - servergroups/status - servers/status - services/status + - sharenetworks/status - subnets/status - trunks/status - users/status diff --git a/config/samples/openstack_v1alpha1_sharenetwork.yaml b/config/samples/openstack_v1alpha1_sharenetwork.yaml new file mode 100644 index 000000000..2396ff7e1 --- /dev/null +++ b/config/samples/openstack_v1alpha1_sharenetwork.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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 ShareNetwork + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/sharenetwork/actuator.go b/internal/controllers/sharenetwork/actuator.go new file mode 100644 index 000000000..d2e6f85d9 --- /dev/null +++ b/internal/controllers/sharenetwork/actuator.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. +*/ + +package sharenetwork + +import ( + "context" + "iter" + "time" + + "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = sharenetworks.ShareNetwork + + createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) +// The frequency to poll when waiting for the resource to become available +const sharenetworkAvailablePollingPeriod = 5 * time.Second +// The frequency to poll when waiting for the resource to be deleted +const sharenetworkDeletingPollingPeriod = 5 * time.Second + +type sharenetworkActuator struct { + osClient osclients.ShareNetworkClient + k8sClient client.Client +} + +var _ createResourceActuator = sharenetworkActuator{} +var _ deleteResourceActuator = sharenetworkActuator{} + +func (sharenetworkActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator sharenetworkActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetShareNetwork(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator sharenetworkActuator) 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 := sharenetworks.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListShareNetworks(ctx, listOpts), true +} + +func (actuator sharenetworkActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + + listOpts := sharenetworks.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListShareNetworks(ctx, listOpts), nil +} + +func (actuator sharenetworkActuator) 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 networkID string + if resource.NetworkRef != nil { + network, networkDepRS := networkDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(networkDepRS) + if network != nil { + networkID = ptr.Deref(network.Status.ID, "") + } + } + + var subnetID string + if resource.SubnetRef != nil { + subnet, subnetDepRS := subnetDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Subnet) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(subnetDepRS) + if subnet != nil { + subnetID = ptr.Deref(subnet.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := sharenetworks.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + NetworkID: networkID, + SubnetID: subnetID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateShareNetwork(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 sharenetworkActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + if resource.Status == ShareNetworkStatusDeleting { + return progress.WaitingOnOpenStack(progress.WaitingOnReady, sharenetworkDeletingPollingPeriod) + } + return progress.WrapError(actuator.osClient.DeleteShareNetwork(ctx, resource.ID)) +} + +func (actuator sharenetworkActuator) 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 := sharenetworks.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.UpdateShareNetwork(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 sharenetworks.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToShareNetworkUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["share_network"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *sharenetworks.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *sharenetworks.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator sharenetworkActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type sharenetworkHelperFactory struct{} + +var _ helperFactory = sharenetworkHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.ShareNetwork, controller interfaces.ResourceController) (sharenetworkActuator, 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 sharenetworkActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return sharenetworkActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewShareNetworkClient() + if err != nil { + return sharenetworkActuator{}, progress.WrapError(err) + } + + return sharenetworkActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (sharenetworkHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return sharenetworkAdapter{obj} +} + +func (sharenetworkHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (sharenetworkHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/sharenetwork/actuator_test.go b/internal/controllers/sharenetwork/actuator_test.go new file mode 100644 index 000000000..ce963f186 --- /dev/null +++ b/internal/controllers/sharenetwork/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 sharenetwork + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks" + 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 sharenetworks.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: sharenetworks.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: sharenetworks.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.ShareNetwork{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.ShareNetworkSpec{ + Resource: &orcv1alpha1.ShareNetworkResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := sharenetworks.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.ShareNetworkResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := sharenetworks.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/sharenetwork/controller.go b/internal/controllers/sharenetwork/controller.go new file mode 100644 index 000000000..aa6fbf1b5 --- /dev/null +++ b/internal/controllers/sharenetwork/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 sharenetwork + +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 = "sharenetwork" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=sharenetworks,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=sharenetworks/status,verbs=get;update;patch + +type sharenetworkReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return sharenetworkReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (sharenetworkReconcilerConstructor) GetName() string { + return controllerName +} + +var networkDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ShareNetworkList, *orcv1alpha1.Network]( + "spec.resource.networkRef", + func(sharenetwork *orcv1alpha1.ShareNetwork) []string { + resource := sharenetwork.Spec.Resource + if resource == nil || resource.NetworkRef == nil { + return nil + } + return []string{string(*resource.NetworkRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var subnetDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ShareNetworkList, *orcv1alpha1.Subnet]( + "spec.resource.subnetRef", + func(sharenetwork *orcv1alpha1.ShareNetwork) []string { + resource := sharenetwork.Spec.Resource + if resource == nil || resource.SubnetRef == nil { + return nil + } + return []string{string(*resource.SubnetRef)} + }, + finalizer, externalObjectFieldOwner, +) + +// SetupWithManager sets up the controller with the Manager. +func (c sharenetworkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + networkWatchEventHandler, err := networkDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + subnetWatchEventHandler, err := subnetDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Network{}, networkWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Network{})), + ). + Watches(&orcv1alpha1.Subnet{}, subnetWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Subnet{})), + ). + For(&orcv1alpha1.ShareNetwork{}) + + if err := errors.Join( + networkDependency.AddToManager(ctx, mgr), + subnetDependency.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, sharenetworkHelperFactory{}, sharenetworkStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/sharenetwork/status.go b/internal/controllers/sharenetwork/status.go new file mode 100644 index 000000000..8b6e7361e --- /dev/null +++ b/internal/controllers/sharenetwork/status.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 sharenetwork + +import ( + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) +// TODO(scaffolding): these are just examples. Change them to the controller's need. +// Ideally, these constants are defined in gophercloud. +const ShareNetworkStatusAvailable = "available" +const ShareNetworkStatusInUse = "in-use" +const ShareNetworkStatusDeleting = "deleting" + +type sharenetworkStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.ShareNetworkApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.ShareNetworkStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.ShareNetwork, *osResourceT, *objectApplyT, *statusApplyT] = sharenetworkStatusWriter{} + +func (sharenetworkStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.ShareNetwork(name, namespace) +} + +func (sharenetworkStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.ShareNetwork, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { + if osResource == nil { + if orcObject.Status.ID == nil { + return metav1.ConditionFalse, nil + } else { + return metav1.ConditionUnknown, nil + } + } + // TODO(scaffolding): add conditions for returning available, for instance: + + if osResource.Status == ShareNetworkStatusAvailable || osResource.Status == ShareNetworkStatusInUse { + return metav1.ConditionTrue, nil + } + + // Otherwise we should continue to poll + return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, sharenetworkAvailablePollingPeriod) +} + +func (sharenetworkStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.ShareNetworkResourceStatus(). + WithNetworkID(osResource.NetworkID). + WithSubnetID(osResource.SubnetID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the ShareNetworkResourceStatus 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/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml new file mode 100644 index 000000000..8b48f92f0 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-create-full +status: + resource: + name: sharenetwork-create-full-override + description: ShareNetwork 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: ShareNetwork + name: sharenetwork-create-full + ref: sharenetwork + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: sharenetwork-create-full + ref: network + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Subnet + name: sharenetwork-create-full + ref: subnet +assertAll: + - celExpr: "sharenetwork.status.id != ''" + - celExpr: "sharenetwork.status.resource.networkID == network.status.id" + - celExpr: "sharenetwork.status.resource.subnetID == subnet.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml new file mode 100644 index 000000000..739222d2d --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-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: Subnet +metadata: + name: sharenetwork-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: ShareNetwork +metadata: + name: sharenetwork-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: sharenetwork-create-full-override + description: ShareNetwork from "create full" test + networkRef: sharenetwork-create-full + subnetRef: sharenetwork-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-create-full/README.md b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/README.md new file mode 100644 index 000000000..239bef04d --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/README.md @@ -0,0 +1,11 @@ +# Create a ShareNetwork with all the options + +## Step 00 + +Create a ShareNetwork 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/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml new file mode 100644 index 000000000..c5abe15b1 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-create-minimal +status: + resource: + name: sharenetwork-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: ShareNetwork + name: sharenetwork-create-minimal + ref: sharenetwork +assertAll: + - celExpr: "sharenetwork.status.id != ''" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..4345eb8eb --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-create-minimal/00-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-create-minimal/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/01-assert.yaml new file mode 100644 index 000000000..4912a5859 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork' in secret.metadata.finalizers" diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/01-delete-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-create-minimal/README.md b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/README.md new file mode 100644 index 000000000..66ca5ba8f --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a ShareNetwork with the minimum options + +## Step 00 + +Create a minimal ShareNetwork, 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/sharenetwork/tests/sharenetwork-dependency/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-assert.yaml new file mode 100644 index 000000000..dfb82c2cc --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/sharenetwork-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/sharenetwork-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-network +status: + conditions: + - type: Available + message: Waiting for Network/sharenetwork-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Network/sharenetwork-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-subnet +status: + conditions: + - type: Available + message: Waiting for Subnet/sharenetwork-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Subnet/sharenetwork-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..1a72dc697 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-network +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-subnet +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + subnetRef: sharenetwork-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: sharenetwork-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-dependency/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-assert.yaml new file mode 100644 index 000000000..bfbc3944b --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-assert.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: ShareNetwork +metadata: + name: sharenetwork-dependency-no-network +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-dependency-no-subnet +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..fed0f4bec --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic sharenetwork-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-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: Subnet +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-dependency/02-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/02-assert.yaml new file mode 100644 index 000000000..6ddbe841c --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/02-assert.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: sharenetwork-dependency + ref: network + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Subnet + name: sharenetwork-dependency + ref: subnet + - apiVersion: v1 + kind: Secret + name: sharenetwork-dependency + ref: secret +assertAll: + - celExpr: "network.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/sharenetwork' in network.metadata.finalizers" + - celExpr: "subnet.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/sharenetwork' in subnet.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/sharenetwork' in secret.metadata.finalizers" diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/02-delete-dependencies.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..9081d277d --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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 network.openstack.k-orc.cloud sharenetwork-dependency --wait=false + namespaced: true + - command: kubectl delete subnet.openstack.k-orc.cloud sharenetwork-dependency --wait=false + namespaced: true + - command: kubectl delete secret sharenetwork-dependency --wait=false + namespaced: true diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-assert.yaml new file mode 100644 index 000000000..3dae927db --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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 network.openstack.k-orc.cloud sharenetwork-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get subnet.openstack.k-orc.cloud sharenetwork-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret sharenetwork-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-delete-resources.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..a38ca83b3 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/03-delete-resources.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-dependency-no-network +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-dependency-no-subnet diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/README.md b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/README.md new file mode 100644 index 000000000..a55d0bd35 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create ShareNetworks referencing non-existing resources. Each ShareNetwork is dependent on other non-existing resource. Verify that the ShareNetworks are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the ShareNetworks 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 ShareNetworks and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-assert.yaml new file mode 100644 index 000000000..850cd808b --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: ShareNetwork +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml new file mode 100644 index 000000000..ea493fcd3 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: ShareNetwork from "import error" test + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: ShareNetwork from "import error" test + # TODO(scaffolding): add any required field diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-import-error/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/01-assert.yaml new file mode 100644 index 000000000..817e891e7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-import-error/01-import-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/01-import-resource.yaml new file mode 100644 index 000000000..4f2ed7619 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: ShareNetwork from "import error" test diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import-error/README.md b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/README.md new file mode 100644 index 000000000..209fa4a6b --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/README.md @@ -0,0 +1,13 @@ +# Import ShareNetwork with more than one matching resources + +## Step 00 + +Create two ShareNetworks with identical specs. + +## Step 01 + +Ensure that an imported ShareNetwork with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml new file mode 100644 index 000000000..5b5b2428f --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml new file mode 100644 index 000000000..ad027092d --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: sharenetwork-import-external + description: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/00-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-import/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml new file mode 100644 index 000000000..8b6e48546 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: sharenetwork-import-external-not-this-one + description: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..d2d1c54cb --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml @@ -0,0 +1,17 @@ +--- +# This `sharenetwork-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: ShareNetwork +metadata: + name: sharenetwork-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: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml new file mode 100644 index 000000000..01f1930fb --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-import-external + ref: sharenetwork1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-import-external-not-this-one + ref: sharenetwork2 +assertAll: + - celExpr: "sharenetwork1.status.id != sharenetwork2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: sharenetwork-import-external + description: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml new file mode 100644 index 000000000..730021dfd --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/README.md b/internal/controllers/sharenetwork/tests/sharenetwork-import/README.md new file mode 100644 index 000000000..ab1dd3733 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/README.md @@ -0,0 +1,18 @@ +# Import ShareNetwork + +## Step 00 + +Import a sharenetwork that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a sharenetwork 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 sharenetwork matching the filter and verify that the observed status on the imported sharenetwork corresponds to the spec of the created sharenetwork. +Also, confirm that it does not adopt any sharenetwork whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml new file mode 100644 index 000000000..b76baae19 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-update + ref: sharenetwork +assertAll: + - celExpr: "!has(sharenetwork.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-update +status: + resource: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml new file mode 100644 index 000000000..f90d9fcd1 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-update/00-secret.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-update/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-assert.yaml new file mode 100644 index 000000000..85b871c7d --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-update +status: + resource: + name: sharenetwork-update-updated + description: sharenetwork-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/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml new file mode 100644 index 000000000..99228476a --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-update +spec: + resource: + name: sharenetwork-update-updated + description: sharenetwork-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml new file mode 100644 index 000000000..88fcf9a88 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: ShareNetwork + name: sharenetwork-update + ref: sharenetwork +assertAll: + - celExpr: "!has(sharenetwork.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: ShareNetwork +metadata: + name: sharenetwork-update +status: + resource: + name: sharenetwork-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/sharenetwork/tests/sharenetwork-update/02-reverted-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-update/README.md b/internal/controllers/sharenetwork/tests/sharenetwork-update/README.md new file mode 100644 index 000000000..48fbf1b56 --- /dev/null +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/README.md @@ -0,0 +1,17 @@ +# Update ShareNetwork + +## Step 00 + +Create a ShareNetwork 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/sharenetwork.go b/internal/osclients/sharenetwork.go new file mode 100644 index 000000000..dcc8b008e --- /dev/null +++ b/internal/osclients/sharenetwork.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/sharedfilesystems/v2/sharenetworks" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type ShareNetworkClient interface { + ListShareNetworks(ctx context.Context, listOpts sharenetworks.ListOptsBuilder) iter.Seq2[*sharenetworks.ShareNetwork, error] + CreateShareNetwork(ctx context.Context, opts sharenetworks.CreateOptsBuilder) (*sharenetworks.ShareNetwork, error) + DeleteShareNetwork(ctx context.Context, resourceID string) error + GetShareNetwork(ctx context.Context, resourceID string) (*sharenetworks.ShareNetwork, error) + UpdateShareNetwork(ctx context.Context, id string, opts sharenetworks.UpdateOptsBuilder) (*sharenetworks.ShareNetwork, error) +} + +type sharenetworkClient struct{ client *gophercloud.ServiceClient } + +// NewShareNetworkClient returns a new OpenStack client. +func NewShareNetworkClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (ShareNetworkClient, error) { + client, err := openstack.NewSharedFilesystemV2(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create sharenetwork service client: %v", err) + } + + return &sharenetworkClient{client}, nil +} + +func (c sharenetworkClient) ListShareNetworks(ctx context.Context, listOpts sharenetworks.ListOptsBuilder) iter.Seq2[*sharenetworks.ShareNetwork, error] { + pager := sharenetworks.List(c.client, listOpts) + return func(yield func(*sharenetworks.ShareNetwork, error) bool) { + _ = pager.EachPage(ctx, yieldPage(sharenetworks.ExtractShareNetworks, yield)) + } +} + +func (c sharenetworkClient) CreateShareNetwork(ctx context.Context, opts sharenetworks.CreateOptsBuilder) (*sharenetworks.ShareNetwork, error) { + return sharenetworks.Create(ctx, c.client, opts).Extract() +} + +func (c sharenetworkClient) DeleteShareNetwork(ctx context.Context, resourceID string) error { + return sharenetworks.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c sharenetworkClient) GetShareNetwork(ctx context.Context, resourceID string) (*sharenetworks.ShareNetwork, error) { + return sharenetworks.Get(ctx, c.client, resourceID).Extract() +} + +func (c sharenetworkClient) UpdateShareNetwork(ctx context.Context, id string, opts sharenetworks.UpdateOptsBuilder) (*sharenetworks.ShareNetwork, error) { + return sharenetworks.Update(ctx, c.client, id, opts).Extract() +} + +type sharenetworkErrorClient struct{ error } + +// NewShareNetworkErrorClient returns a ShareNetworkClient in which every method returns the given error. +func NewShareNetworkErrorClient(e error) ShareNetworkClient { + return sharenetworkErrorClient{e} +} + +func (e sharenetworkErrorClient) ListShareNetworks(_ context.Context, _ sharenetworks.ListOptsBuilder) iter.Seq2[*sharenetworks.ShareNetwork, error] { + return func(yield func(*sharenetworks.ShareNetwork, error) bool) { + yield(nil, e.error) + } +} + +func (e sharenetworkErrorClient) CreateShareNetwork(_ context.Context, _ sharenetworks.CreateOptsBuilder) (*sharenetworks.ShareNetwork, error) { + return nil, e.error +} + +func (e sharenetworkErrorClient) DeleteShareNetwork(_ context.Context, _ string) error { + return e.error +} + +func (e sharenetworkErrorClient) GetShareNetwork(_ context.Context, _ string) (*sharenetworks.ShareNetwork, error) { + return nil, e.error +} + +func (e sharenetworkErrorClient) UpdateShareNetwork(_ context.Context, _ string, _ sharenetworks.UpdateOptsBuilder) (*sharenetworks.ShareNetwork, error) { + return nil, e.error +} diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 756e1f09e..8bbac5ae3 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2169,6 +2169,7 @@ _Appears in:_ - [ServerResourceSpec](#serverresourcespec) - [ServerSchedulerHints](#serverschedulerhints) - [ServerVolumeSpec](#servervolumespec) +- [ShareNetworkResourceSpec](#sharenetworkresourcespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) @@ -2586,6 +2587,8 @@ _Appears in:_ - [ServerResourceSpec](#serverresourcespec) - [ServiceFilter](#servicefilter) - [ServiceResourceSpec](#serviceresourcespec) +- [ShareNetworkFilter](#sharenetworkfilter) +- [ShareNetworkResourceSpec](#sharenetworkresourcespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) - [TrunkFilter](#trunkfilter) @@ -4254,6 +4257,12 @@ _Appears in:_ | `resource` _[ServiceResourceStatus](#serviceresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| + + + + + + #### Subnet From 59a450586a16a77974834ab20bd699293a4fafd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:43:53 +0000 Subject: [PATCH 193/237] :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 4.0.0 to 4.1.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd...d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor 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 95d8eaaf5..464be4d23 100644 --- a/.github/workflows/container_image.yaml +++ b/.github/workflows/container_image.yaml @@ -25,7 +25,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4.0.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # tag=v4.1.0 - name: Build and push images run: | diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index 7a9319faa..559d08217 100644 --- a/.github/workflows/release_image.yaml +++ b/.github/workflows/release_image.yaml @@ -25,7 +25,7 @@ jobs: fetch-tags: true persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # tag=v4.0.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # tag=v4.1.0 - name: Install build dependencies run: sudo apt-get install -y libgpgme-dev From fa2108c331246dece2438e4858e37a733a888bcd Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Mon, 23 Feb 2026 17:00:40 +0000 Subject: [PATCH 194/237] Added ShareNetwork Controller for Manila Service Implements ShareNetwork controller to manage Manila share networks. - E2E tests included - API configured - Manila enabled in CI Signed-off-by: Daniel Lawton Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/e2e.yaml | 1 + PROJECT | 8 + README.md | 1 + api/v1alpha1/sharenetwork_types.go | 58 ++- api/v1alpha1/zz_generated.deepcopy.go | 165 +++++++++ .../zz_generated.sharenetwork-resource.go | 179 +++++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 295 ++++++++++++++- cmd/resource-generator/main.go | 3 + .../openstack.k-orc.cloud_sharenetworks.yaml | 349 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + .../bases/orc.clusterserviceversion.yaml | 10 + config/samples/kustomization.yaml | 1 + .../openstack_v1alpha1_sharenetwork.yaml | 37 +- internal/controllers/sharenetwork/actuator.go | 52 +-- internal/controllers/sharenetwork/status.go | 63 +++- .../sharenetwork-create-full/00-assert.yaml | 11 +- .../00-create-resource.yaml | 15 +- .../00-assert.yaml | 12 +- .../00-create-resource.yaml | 4 - .../00-create-resources-missing-deps.yaml | 74 +++- .../01-create-dependencies.yaml | 9 +- .../00-create-resources.yaml | 64 +++- .../tests/sharenetwork-import/00-assert.yaml | 2 +- .../00-import-resource.yaml | 1 - .../tests/sharenetwork-import/01-assert.yaml | 1 - .../01-create-trap-resource.yaml | 31 +- .../tests/sharenetwork-import/02-assert.yaml | 1 - .../02-create-resource.yaml | 32 +- .../tests/sharenetwork-update/00-assert.yaml | 1 - .../00-minimal-resource.yaml | 3 - .../tests/sharenetwork-update/01-assert.yaml | 1 - .../01-updated-resource.yaml | 1 - .../tests/sharenetwork-update/02-assert.yaml | 1 - .../sharenetwork/zz_generated.adapter.go | 88 +++++ .../sharenetwork/zz_generated.controller.go | 45 +++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/sharenetwork.go | 131 +++++++ internal/osclients/sharenetwork.go | 4 +- internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/sharenetwork.go | 281 ++++++++++++++ .../api/v1alpha1/sharenetworkfilter.go | 52 +++ .../api/v1alpha1/sharenetworkimport.go | 48 +++ .../api/v1alpha1/sharenetworkresourcespec.go | 70 ++++ .../v1alpha1/sharenetworkresourcestatus.go | 133 +++++++ .../api/v1alpha1/sharenetworkspec.go | 79 ++++ .../api/v1alpha1/sharenetworkstatus.go | 66 ++++ .../applyconfiguration/internal/internal.go | 127 +++++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../api/v1alpha1/fake/fake_sharenetwork.go | 53 +++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../typed/api/v1alpha1/sharenetwork.go | 74 ++++ .../api/v1alpha1/interface.go | 7 + .../api/v1alpha1/sharenetwork.go | 102 +++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + .../listers/api/v1alpha1/sharenetwork.go | 70 ++++ test/apivalidations/sharenetwork_test.go | 124 +++++++ website/docs/crd-reference.md | 138 +++++++ 64 files changed, 3093 insertions(+), 139 deletions(-) create mode 100644 api/v1alpha1/zz_generated.sharenetwork-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml create mode 100644 internal/controllers/sharenetwork/zz_generated.adapter.go create mode 100644 internal/controllers/sharenetwork/zz_generated.controller.go create mode 100644 internal/osclients/mock/sharenetwork.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetwork.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_sharenetwork.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/sharenetwork.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/sharenetwork.go create mode 100644 pkg/clients/listers/api/v1alpha1/sharenetwork.go create mode 100644 test/apivalidations/sharenetwork_test.go diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index f86bd3c55..8f9e0220d 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -41,6 +41,7 @@ jobs: enabled_services: "openstack-cli-server,neutron-trunk,neutron-port-trusted-vif" conf_overrides: | enable_plugin neutron https://github.com/openstack/neutron ${{ matrix.openstack_version }} + enable_plugin manila https://github.com/openstack/manila ${{ matrix.openstack_version }} [[post-config|/etc/nova/nova.conf]] [filter_scheduler] diff --git a/PROJECT b/PROJECT index 73daa42d0..e5a188a8b 100644 --- a/PROJECT +++ b/PROJECT @@ -160,6 +160,14 @@ resources: kind: Service path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: ShareNetwork + 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 f2dac4c5b..706cce900 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ kubectl delete -f $ORC_RELEASE | server | | ◐ | ◐ | | server group | | ✔ | ✔ | | service | | ✔ | ✔ | +| share network | | ◐ | ◐ | | subnet | | ◐ | ◐ | | trunk | | ✔ | ✔ | | user | | ◐ | ◐ | diff --git a/api/v1alpha1/sharenetwork_types.go b/api/v1alpha1/sharenetwork_types.go index f0a5a813a..83ef4dfca 100644 --- a/api/v1alpha1/sharenetwork_types.go +++ b/api/v1alpha1/sharenetwork_types.go @@ -16,7 +16,10 @@ limitations under the License. package v1alpha1 +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + // ShareNetworkResourceSpec contains the desired state of the resource. +// +kubebuilder:validation:XValidation:rule="has(self.networkRef) == has(self.subnetRef)",message="networkRef and subnetRef must be specified together" type ShareNetworkResourceSpec struct { // name will be the name of the created resource. If not specified, the // name of the ORC object will be used. @@ -38,14 +41,6 @@ type ShareNetworkResourceSpec struct { // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="subnetRef is immutable" SubnetRef *KubernetesNameRef `json:"subnetRef,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/sharedfilesystems/v2/sharenetworks - // - // 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"` } // ShareNetworkFilter defines an existing resource by its properties @@ -60,15 +55,11 @@ type ShareNetworkFilter struct { // +kubebuilder:validation:MaxLength:=255 // +optional Description *string `json:"description,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ListOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks } // ShareNetworkResourceStatus represents the observed state of the resource. type ShareNetworkResourceStatus struct { - // name is a Human-readable name for the resource. Might not be unique. + // name is a Human-readable name for the resource. // +kubebuilder:validation:MaxLength=1024 // +optional Name string `json:"name,omitempty"` @@ -78,17 +69,44 @@ type ShareNetworkResourceStatus struct { // +optional Description string `json:"description,omitempty"` - // networkID is the ID of the Network to which the resource is associated. + // neutronNetID is the Neutron network ID. + // +kubebuilder:validation:MaxLength=1024 + // +optional + NeutronNetID string `json:"neutronNetID,omitempty"` + + // neutronSubnetID is the Neutron subnet ID. // +kubebuilder:validation:MaxLength=1024 // +optional - NetworkID string `json:"networkID,omitempty"` + NeutronSubnetID string `json:"neutronSubnetID,omitempty"` - // subnetID is the ID of the Subnet to which the resource is associated. + // networkType is the network type (e.g., vlan, vxlan, flat). // +kubebuilder:validation:MaxLength=1024 // +optional - SubnetID string `json:"subnetID,omitempty"` + NetworkType string `json:"networkType,omitempty"` + + // segmentationID is the segmentation ID of the network. + // +optional + SegmentationID *int32 `json:"segmentationID,omitempty"` - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ShareNetwork structure from - // github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks + // cidr is the CIDR of the subnet. + // +kubebuilder:validation:MaxLength=1024 + // +optional + CIDR string `json:"cidr"` + + // ipVersion is the IP version (4 or 6). + // +optional + IPVersion *int32 `json:"ipVersion,omitempty"` + + // projectID is the ID of the project that owns the share network. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // createdAt shows the date and time when the resource was created. + // +optional + CreatedAt *metav1.Time `json:"createdAt,omitempty"` + + // updatedAt shows the date and time when the resource was updated. + // +optional + UpdatedAt *metav1.Time `json:"updatedAt,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b3d1b3950..ead3ef708 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5599,6 +5599,33 @@ func (in *ServiceStatus) DeepCopy() *ServiceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetwork) DeepCopyInto(out *ShareNetwork) { + *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 ShareNetwork. +func (in *ShareNetwork) DeepCopy() *ShareNetwork { + if in == nil { + return nil + } + out := new(ShareNetwork) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ShareNetwork) 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 *ShareNetworkFilter) DeepCopyInto(out *ShareNetworkFilter) { *out = *in @@ -5624,6 +5651,63 @@ func (in *ShareNetworkFilter) DeepCopy() *ShareNetworkFilter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkImport) DeepCopyInto(out *ShareNetworkImport) { + *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(ShareNetworkFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkImport. +func (in *ShareNetworkImport) DeepCopy() *ShareNetworkImport { + if in == nil { + return nil + } + out := new(ShareNetworkImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkList) DeepCopyInto(out *ShareNetworkList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ShareNetwork, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkList. +func (in *ShareNetworkList) DeepCopy() *ShareNetworkList { + if in == nil { + return nil + } + out := new(ShareNetworkList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ShareNetworkList) 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 *ShareNetworkResourceSpec) DeepCopyInto(out *ShareNetworkResourceSpec) { *out = *in @@ -5662,6 +5746,24 @@ func (in *ShareNetworkResourceSpec) DeepCopy() *ShareNetworkResourceSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ShareNetworkResourceStatus) DeepCopyInto(out *ShareNetworkResourceStatus) { *out = *in + if in.SegmentationID != nil { + in, out := &in.SegmentationID, &out.SegmentationID + *out = new(int32) + **out = **in + } + if in.IPVersion != nil { + in, out := &in.IPVersion, &out.IPVersion + *out = new(int32) + **out = **in + } + if in.CreatedAt != nil { + in, out := &in.CreatedAt, &out.CreatedAt + *out = (*in).DeepCopy() + } + if in.UpdatedAt != nil { + in, out := &in.UpdatedAt, &out.UpdatedAt + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkResourceStatus. @@ -5674,6 +5776,69 @@ func (in *ShareNetworkResourceStatus) DeepCopy() *ShareNetworkResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkSpec) DeepCopyInto(out *ShareNetworkSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(ShareNetworkImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(ShareNetworkResourceSpec) + (*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 ShareNetworkSpec. +func (in *ShareNetworkSpec) DeepCopy() *ShareNetworkSpec { + if in == nil { + return nil + } + out := new(ShareNetworkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShareNetworkStatus) DeepCopyInto(out *ShareNetworkStatus) { + *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(ShareNetworkResourceStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkStatus. +func (in *ShareNetworkStatus) DeepCopy() *ShareNetworkStatus { + if in == nil { + return nil + } + out := new(ShareNetworkStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Subnet) DeepCopyInto(out *Subnet) { *out = *in diff --git a/api/v1alpha1/zz_generated.sharenetwork-resource.go b/api/v1alpha1/zz_generated.sharenetwork-resource.go new file mode 100644 index 000000000..3c73d2b69 --- /dev/null +++ b/api/v1alpha1/zz_generated.sharenetwork-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" +) + +// ShareNetworkImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type ShareNetworkImport 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 *ShareNetworkFilter `json:"filter,omitempty"` +} + +// ShareNetworkSpec 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 ShareNetworkSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *ShareNetworkImport `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 *ShareNetworkResourceSpec `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"` +} + +// ShareNetworkStatus defines the observed state of an ORC resource. +type ShareNetworkStatus 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 *ShareNetworkResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &ShareNetwork{} + +func (i *ShareNetwork) 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" + +// ShareNetwork is the Schema for an ORC resource. +type ShareNetwork 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 ShareNetworkSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status ShareNetworkStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ShareNetworkList contains a list of ShareNetwork. +type ShareNetworkList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of ShareNetwork. + // +required + Items []ShareNetwork `json:"items"` +} + +func (l *ShareNetworkList) GetItems() []ShareNetwork { + return l.Items +} + +func init() { + SchemeBuilder.Register(&ShareNetwork{}, &ShareNetworkList{}) +} + +func (i *ShareNetwork) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &ShareNetwork{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index c8a624acc..9addc552d 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/server" "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/sharenetwork" "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" @@ -133,6 +134,7 @@ func main() { volumetype.New(scopeFactory), domain.New(scopeFactory), service.New(scopeFactory), + sharenetwork.New(scopeFactory), keypair.New(scopeFactory), group.New(scopeFactory), role.New(scopeFactory), diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index b5e5818bc..8960fadc4 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -223,9 +223,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetwork": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetwork(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkImport": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkList": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkList(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Subnet": schema_openstack_resource_controller_v2_api_v1alpha1_Subnet(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetFilter": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetGateway": schema_openstack_resource_controller_v2_api_v1alpha1_SubnetGateway(ref), @@ -10570,6 +10575,57 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref comm } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetwork(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetwork 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.ShareNetworkSpec"), + }, + }, + "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.ShareNetworkStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -10597,6 +10653,85 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkFilter(ref } } +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkImport 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.ShareNetworkFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkList contains a list of ShareNetwork.", + 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 ShareNetwork.", + 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.ShareNetwork"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetwork", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -10647,7 +10782,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceSt Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "name is a Human-readable name for the resource. Might not be unique.", + Description: "name is a Human-readable name for the resource.", Type: []string{"string"}, Format: "", }, @@ -10659,23 +10794,173 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkResourceSt Format: "", }, }, - "networkID": { + "neutronNetID": { SchemaProps: spec.SchemaProps{ - Description: "networkID is the ID of the Network to which the resource is associated.", + Description: "neutronNetID is the Neutron network ID.", Type: []string{"string"}, Format: "", }, }, - "subnetID": { + "neutronSubnetID": { SchemaProps: spec.SchemaProps{ - Description: "subnetID is the ID of the Subnet to which the resource is associated.", + Description: "neutronSubnetID is the Neutron subnet ID.", Type: []string{"string"}, Format: "", }, }, + "networkType": { + SchemaProps: spec.SchemaProps{ + Description: "networkType is the network type (e.g., vlan, vxlan, flat).", + Type: []string{"string"}, + Format: "", + }, + }, + "segmentationID": { + SchemaProps: spec.SchemaProps{ + Description: "segmentationID is the segmentation ID of the network.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "cidr": { + SchemaProps: spec.SchemaProps{ + Description: "cidr is the CIDR of the subnet.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "ipVersion": { + SchemaProps: spec.SchemaProps{ + Description: "ipVersion is the IP version (4 or 6).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the ID of the project that owns the share network.", + Type: []string{"string"}, + Format: "", + }, + }, + "createdAt": { + SchemaProps: spec.SchemaProps{ + Description: "createdAt shows the date and time when the resource was created.", + 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.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkSpec 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.ShareNetworkImport"), + }, + }, + "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.ShareNetworkResourceSpec"), + }, + }, + "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.ShareNetworkImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ShareNetworkStatus 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.ShareNetworkResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 2ff83df6e..609bf9084 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -160,6 +160,9 @@ var resources []templateFields = []templateFields{ { Name: "Service", }, + { + Name: "ShareNetwork", + }, { Name: "KeyPair", UsesNameAsID: true, // Keypairs uses name as ID, not UUID diff --git a/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml b/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml new file mode 100644 index 000000000..bd42cf06c --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml @@ -0,0 +1,349 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: sharenetworks.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: ShareNetwork + listKind: ShareNetworkList + plural: sharenetworks + singular: sharenetwork + 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: ShareNetwork is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + description: + description: description of the existing resource + maxLength: 255 + minLength: 1 + type: string + 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: + 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 + networkRef: + description: networkRef is a reference to the ORC Network which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: networkRef is immutable + rule: self == oldSelf + subnetRef: + description: subnetRef is a reference to the ORC Subnet which + this resource is associated with. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: subnetRef is immutable + rule: self == oldSelf + type: object + x-kubernetes-validations: + - message: networkRef and subnetRef must be specified together + rule: has(self.networkRef) == has(self.subnetRef) + 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: + cidr: + description: cidr is the CIDR of the subnet. + maxLength: 1024 + type: string + createdAt: + description: createdAt shows the date and time when the resource + was created. + format: date-time + type: string + description: + description: description is a human-readable description for the + resource. + maxLength: 1024 + type: string + ipVersion: + description: ipVersion is the IP version (4 or 6). + format: int32 + type: integer + name: + description: name is a Human-readable name for the resource. + maxLength: 1024 + type: string + networkType: + description: networkType is the network type (e.g., vlan, vxlan, + flat). + maxLength: 1024 + type: string + neutronNetID: + description: neutronNetID is the Neutron network ID. + maxLength: 1024 + type: string + neutronSubnetID: + description: neutronSubnetID is the Neutron subnet ID. + maxLength: 1024 + type: string + projectID: + description: projectID is the ID of the project that owns the + share network. + maxLength: 1024 + type: string + segmentationID: + description: segmentationID is the segmentation ID of the network. + format: int32 + type: integer + updatedAt: + description: updatedAt shows the date and time when the resource + was updated. + format: date-time + 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 85a318b42..26b47f63f 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -22,6 +22,7 @@ resources: - bases/openstack.k-orc.cloud_servers.yaml - bases/openstack.k-orc.cloud_servergroups.yaml - bases/openstack.k-orc.cloud_services.yaml +- bases/openstack.k-orc.cloud_sharenetworks.yaml - bases/openstack.k-orc.cloud_subnets.yaml - bases/openstack.k-orc.cloud_trunks.yaml - bases/openstack.k-orc.cloud_users.yaml diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 0b7164e78..742c646bc 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -104,11 +104,21 @@ spec: kind: Service name: services.openstack.k-orc.cloud version: v1alpha1 + - description: ShareNetwork is the Schema for an ORC resource. + displayName: Share Network + kind: ShareNetwork + name: sharenetworks.openstack.k-orc.cloud + version: v1alpha1 - description: Subnet is the Schema for an ORC resource. displayName: Subnet kind: Subnet name: subnets.openstack.k-orc.cloud version: v1alpha1 + - description: Trunk is the Schema for an ORC resource. + displayName: Trunk + kind: Trunk + name: trunks.openstack.k-orc.cloud + version: v1alpha1 - description: Volume is the Schema for an ORC resource. displayName: Volume kind: Volume diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 4b86755db..8a50ba039 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -20,6 +20,7 @@ resources: - openstack_v1alpha1_server.yaml - openstack_v1alpha1_servergroup.yaml - openstack_v1alpha1_service.yaml +- openstack_v1alpha1_sharenetwork.yaml - openstack_v1alpha1_subnet.yaml - openstack_v1alpha1_trunk.yaml - openstack_v1alpha1_user.yaml diff --git a/config/samples/openstack_v1alpha1_sharenetwork.yaml b/config/samples/openstack_v1alpha1_sharenetwork.yaml index 2396ff7e1..cd045a6a2 100644 --- a/config/samples/openstack_v1alpha1_sharenetwork.yaml +++ b/config/samples/openstack_v1alpha1_sharenetwork.yaml @@ -1,14 +1,45 @@ --- +# Create a Network for the ShareNetwork +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-sample +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Network for ShareNetwork sample +--- +# Create a Subnet for the ShareNetwork +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-sample +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-sample + ipVersion: 4 + cidr: 192.168.100.0/24 + description: Subnet for ShareNetwork sample +--- +# Create a ShareNetwork for Manila shares apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-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 ShareNetwork - # TODO(scaffolding): Add all fields the resource supports + name: my-share-network + description: Sample ShareNetwork for Manila shared filesystems + networkRef: sharenetwork-sample + subnetRef: sharenetwork-sample diff --git a/internal/controllers/sharenetwork/actuator.go b/internal/controllers/sharenetwork/actuator.go index d2e6f85d9..fe08ea788 100644 --- a/internal/controllers/sharenetwork/actuator.go +++ b/internal/controllers/sharenetwork/actuator.go @@ -19,7 +19,6 @@ package sharenetwork import ( "context" "iter" - "time" "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks" corev1 "k8s.io/api/core/v1" @@ -44,10 +43,6 @@ type ( resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] ) -// The frequency to poll when waiting for the resource to become available -const sharenetworkAvailablePollingPeriod = 5 * time.Second -// The frequency to poll when waiting for the resource to be deleted -const sharenetworkDeletingPollingPeriod = 5 * time.Second type sharenetworkActuator struct { osClient osclients.ShareNetworkClient @@ -75,27 +70,17 @@ func (actuator sharenetworkActuator) 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 := sharenetworks.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + Name: getResourceName(orcObject), } return actuator.osClient.ListShareNetworks(ctx, listOpts), true } func (actuator sharenetworkActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter - listOpts := sharenetworks.ListOpts{ Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - // TODO(scaffolding): Add more import filters + Description: ptr.Deref(filter.Description, ""), } return actuator.osClient.ListShareNetworks(ctx, listOpts), nil @@ -114,9 +99,7 @@ func (actuator sharenetworkActuator) CreateResource(ctx context.Context, obj orc var networkID string if resource.NetworkRef != nil { network, networkDepRS := networkDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Network) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(networkDepRS) if network != nil { @@ -127,9 +110,7 @@ func (actuator sharenetworkActuator) CreateResource(ctx context.Context, obj orc var subnetID string if resource.SubnetRef != nil { subnet, subnetDepRS := subnetDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Subnet) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, + ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(subnetDepRS) if subnet != nil { @@ -140,11 +121,10 @@ func (actuator sharenetworkActuator) CreateResource(ctx context.Context, obj orc return nil, reconcileStatus } createOpts := sharenetworks.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - NetworkID: networkID, - SubnetID: subnetID, - // TODO(scaffolding): Add more fields + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + NeutronNetID: networkID, + NeutronSubnetID: subnetID, } osResource, err := actuator.osClient.CreateShareNetwork(ctx, createOpts) @@ -160,9 +140,6 @@ func (actuator sharenetworkActuator) CreateResource(ctx context.Context, obj orc } func (actuator sharenetworkActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - if resource.Status == ShareNetworkStatusDeleting { - return progress.WaitingOnOpenStack(progress.WaitingOnReady, sharenetworkDeletingPollingPeriod) - } return progress.WrapError(actuator.osClient.DeleteShareNetwork(ctx, resource.ID)) } @@ -180,8 +157,6 @@ func (actuator sharenetworkActuator) updateResource(ctx context.Context, obj orc 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( @@ -193,20 +168,17 @@ func (actuator sharenetworkActuator) updateResource(ctx context.Context, obj orc } _, err = actuator.osClient.UpdateShareNetwork(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 { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } return progress.WrapError(err) } return progress.NeedsRefresh() } -func needsUpdate(updateOpts sharenetworks.UpdateOpts) (bool, error) { +func needsUpdate(updateOpts sharenetworks.UpdateOptsBuilder) (bool, error) { updateOptsMap, err := updateOpts.ToShareNetworkUpdateMap() if err != nil { return false, err diff --git a/internal/controllers/sharenetwork/status.go b/internal/controllers/sharenetwork/status.go index 8b6e7361e..fe9da2501 100644 --- a/internal/controllers/sharenetwork/status.go +++ b/internal/controllers/sharenetwork/status.go @@ -25,11 +25,6 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" ) -// TODO(scaffolding): these are just examples. Change them to the controller's need. -// Ideally, these constants are defined in gophercloud. -const ShareNetworkStatusAvailable = "available" -const ShareNetworkStatusInUse = "in-use" -const ShareNetworkStatusDeleting = "deleting" type sharenetworkStatusWriter struct{} @@ -46,32 +41,60 @@ func (sharenetworkStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.S if osResource == nil { if orcObject.Status.ID == nil { return metav1.ConditionFalse, nil - } else { - return metav1.ConditionUnknown, nil } + return metav1.ConditionUnknown, nil } - // TODO(scaffolding): add conditions for returning available, for instance: - if osResource.Status == ShareNetworkStatusAvailable || osResource.Status == ShareNetworkStatusInUse { - return metav1.ConditionTrue, nil - } - - // Otherwise we should continue to poll - return metav1.ConditionFalse, progress.WaitingOnOpenStack(progress.WaitingOnReady, sharenetworkAvailablePollingPeriod) + // Share networks become available immediately after creation + // No async operations to wait for + return metav1.ConditionTrue, nil } func (sharenetworkStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { - resourceStatus := orcapplyconfigv1alpha1.ShareNetworkResourceStatus(). - WithNetworkID(osResource.NetworkID). - WithSubnetID(osResource.SubnetID). - WithName(osResource.Name) + resourceStatus := orcapplyconfigv1alpha1.ShareNetworkResourceStatus() + + if osResource.Name != "" { + resourceStatus.WithName(osResource.Name) + } + + if osResource.NeutronNetID != "" { + resourceStatus.WithNeutronNetID(osResource.NeutronNetID) + } + + if osResource.NeutronSubnetID != "" { + resourceStatus.WithNeutronSubnetID(osResource.NeutronSubnetID) + } + + if osResource.NetworkType != "" { + resourceStatus.WithNetworkType(osResource.NetworkType) + } - // TODO(scaffolding): add all of the fields supported in the ShareNetworkResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional + // Always set CIDR field, even if empty, so it's always present in status + resourceStatus.WithCIDR(osResource.CIDR) + + if osResource.ProjectID != "" { + resourceStatus.WithProjectID(osResource.ProjectID) + } if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } + if osResource.SegmentationID != 0 { + resourceStatus.WithSegmentationID(int32(osResource.SegmentationID)) + } + + if osResource.IPVersion != 0 { + resourceStatus.WithIPVersion(int32(osResource.IPVersion)) + } + + if !osResource.CreatedAt.IsZero() { + resourceStatus.WithCreatedAt(metav1.Time{Time: osResource.CreatedAt}) + } + + if !osResource.UpdatedAt.IsZero() { + resourceStatus.WithUpdatedAt(metav1.Time{Time: osResource.UpdatedAt}) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml index 8b48f92f0..6b5e4a0ab 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-assert.yaml @@ -7,7 +7,6 @@ status: resource: name: sharenetwork-create-full-override description: ShareNetwork from "create full" test - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -33,6 +32,10 @@ resourceRefs: ref: subnet assertAll: - celExpr: "sharenetwork.status.id != ''" - - celExpr: "sharenetwork.status.resource.networkID == network.status.id" - - celExpr: "sharenetwork.status.resource.subnetID == subnet.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "sharenetwork.status.resource.neutronNetID == network.status.id" + - celExpr: "sharenetwork.status.resource.neutronSubnetID == subnet.status.id" + - celExpr: "sharenetwork.status.resource.projectID != ''" + - celExpr: "has(sharenetwork.status.resource.createdAt)" +# dlawton(TODO): +# Currently missing checks for networkType, segmentationID, CIDR, ipVersion, and updatedAt +# this controller will be kept as partially implemented until missing validations are included. \ No newline at end of file diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml index 739222d2d..7b857ba75 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-full/00-create-resource.yaml @@ -5,12 +5,11 @@ metadata: name: sharenetwork-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: sharenetwork-create-full-network --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Subnet @@ -18,12 +17,14 @@ metadata: name: sharenetwork-create-full spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: sharenetwork-create-full-subnet + networkRef: sharenetwork-create-full + ipVersion: 4 + cidr: 192.168.200.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork @@ -31,7 +32,6 @@ metadata: name: sharenetwork-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 @@ -40,4 +40,3 @@ spec: description: ShareNetwork from "create full" test networkRef: sharenetwork-create-full subnetRef: sharenetwork-create-full - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml index c5abe15b1..67ae894a6 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-assert.yaml @@ -6,7 +6,6 @@ metadata: status: resource: name: sharenetwork-create-minimal - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -24,4 +23,13 @@ resourceRefs: ref: sharenetwork assertAll: - celExpr: "sharenetwork.status.id != ''" - # TODO(scaffolding): Add more checks + - celExpr: "!has(sharenetwork.status.resource.neutronNetID)" + - celExpr: "!has(sharenetwork.status.resource.neutronSubnetID)" + - celExpr: "sharenetwork.status.resource.name == 'sharenetwork-create-minimal'" + - celExpr: "sharenetwork.status.resource.projectID != ''" + - celExpr: "has(sharenetwork.status.resource.createdAt)" + - celExpr: "has(sharenetwork.status.resource.cidr)" +# dlawton(TODO): +# Currently missing checks for networkType, segmentationID, ipVersion, and updatedAt +# this controller will be kept as partially implemented until missing validations are included. + diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml index 4345eb8eb..a0e8cd092 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-create-minimal/00-create-resource.yaml @@ -1,14 +1,10 @@ ---- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-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/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml index 1a72dc697..a47682eb6 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/00-create-resources-missing-deps.yaml @@ -1,30 +1,90 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-dependency-no-network-available +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-dependency-no-network-available +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-dependency-no-network-available + ipVersion: 4 + cidr: 192.168.203.0/24 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-dependency-no-network spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: networkRef: sharenetwork-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- + subnetRef: sharenetwork-dependency-no-network-available +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-dependency-no-subnet-available +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-dependency-no-subnet spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed resource: + networkRef: sharenetwork-dependency-no-subnet-available subnetRef: sharenetwork-dependency - # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-dependency-no-secret-available +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-dependency-no-secret-available +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-dependency-no-secret-available + ipVersion: 4 + cidr: 192.168.204.0/24 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork @@ -32,9 +92,9 @@ metadata: name: sharenetwork-dependency-no-secret spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: sharenetwork-dependency managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: sharenetwork-dependency-no-secret-available + subnetRef: sharenetwork-dependency-no-secret-available diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml index fed0f4bec..a42c65d27 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-dependency/01-create-dependencies.yaml @@ -11,11 +11,9 @@ metadata: name: sharenetwork-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 @@ -24,9 +22,10 @@ metadata: name: sharenetwork-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created cloudName: openstack secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + networkRef: sharenetwork-dependency + ipVersion: 4 + cidr: 192.168.202.0/24 diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml index ea493fcd3..849161e71 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import-error/00-create-resources.yaml @@ -1,28 +1,84 @@ --- +# Create Network for first ShareNetwork +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-import-error-1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# Create Subnet for first ShareNetwork +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-import-error-1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-import-error-1 + ipVersion: 4 + cidr: 192.168.207.0/24 +--- +# Create first ShareNetwork with identical description apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-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: ShareNetwork from "import error" test - # TODO(scaffolding): add any required field + networkRef: sharenetwork-import-error-1 + subnetRef: sharenetwork-import-error-1 +--- +# Create Network for second ShareNetwork +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-import-error-2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# Create Subnet for second ShareNetwork +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-import-error-2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-import-error-2 + ipVersion: 4 + cidr: 192.168.208.0/24 --- +# Create second ShareNetwork with identical description apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-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: ShareNetwork from "import error" test - # TODO(scaffolding): add any required field + networkRef: sharenetwork-import-error-2 + subnetRef: sharenetwork-import-error-2 diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml index 5b5b2428f..29e52d160 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import/00-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-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/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml index ad027092d..fd92c10da 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/00-import-resource.yaml @@ -12,4 +12,3 @@ spec: filter: name: sharenetwork-import-external description: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test - # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml index 8b6e48546..a0259a713 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-assert.yaml @@ -16,7 +16,6 @@ status: resource: name: sharenetwork-import-external-not-this-one description: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test - # TODO(scaffolding): Add fields necessary to match filter --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml index d2d1c54cb..08839d2eb 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/01-create-trap-resource.yaml @@ -1,4 +1,31 @@ --- +# Create Network for trap resource +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-import-trap +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# Create Subnet for trap resource +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-import-trap +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-import-trap + ipVersion: 4 + cidr: 192.168.205.0/24 +--- # This `sharenetwork-import-external-not-this-one` resource serves two purposes: # - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) # - ensure that importing a resource which name is a substring of it will not pick this one. @@ -8,10 +35,10 @@ metadata: name: sharenetwork-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: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test - # TODO(scaffolding): Add fields necessary to match filter + networkRef: sharenetwork-import-trap + subnetRef: sharenetwork-import-trap diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml index 01f1930fb..1b60f1a48 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-assert.yaml @@ -30,4 +30,3 @@ status: resource: name: sharenetwork-import-external description: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml index 730021dfd..0c753237b 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-import/02-create-resource.yaml @@ -1,14 +1,42 @@ --- +# Create Network for the resource to be imported +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: sharenetwork-import-external +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +# Create Subnet for the resource to be imported +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Subnet +metadata: + name: sharenetwork-import-external +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: sharenetwork-import-external + ipVersion: 4 + cidr: 192.168.206.0/24 +--- +# Create the ShareNetwork to be imported apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ShareNetwork metadata: name: sharenetwork-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: ShareNetwork sharenetwork-import-external from "sharenetwork-import" test - # TODO(scaffolding): Add fields necessary to match filter + networkRef: sharenetwork-import-external + subnetRef: sharenetwork-import-external diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml index b76baae19..5eacdf53e 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-assert.yaml @@ -16,7 +16,6 @@ metadata: status: resource: name: sharenetwork-update - # TODO(scaffolding): Add matches for more fields conditions: - type: Available status: "True" diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml index f90d9fcd1..13c6069c2 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/00-minimal-resource.yaml @@ -5,10 +5,7 @@ metadata: name: sharenetwork-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/sharenetwork/tests/sharenetwork-update/01-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-assert.yaml index 85b871c7d..a2669ad64 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-update/01-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-assert.yaml @@ -7,7 +7,6 @@ status: resource: name: sharenetwork-update-updated description: sharenetwork-update-updated - # TODO(scaffolding): match all fields that were modified conditions: - type: Available status: "True" diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml index 99228476a..d99bfb5e6 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/01-updated-resource.yaml @@ -7,4 +7,3 @@ spec: resource: name: sharenetwork-update-updated description: sharenetwork-update-updated - # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml b/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml index 88fcf9a88..5eacdf53e 100644 --- a/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml +++ b/internal/controllers/sharenetwork/tests/sharenetwork-update/02-assert.yaml @@ -16,7 +16,6 @@ metadata: status: resource: name: sharenetwork-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value conditions: - type: Available status: "True" diff --git a/internal/controllers/sharenetwork/zz_generated.adapter.go b/internal/controllers/sharenetwork/zz_generated.adapter.go new file mode 100644 index 000000000..b89627a5b --- /dev/null +++ b/internal/controllers/sharenetwork/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 sharenetwork + +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.ShareNetwork + orcObjectListT = orcv1alpha1.ShareNetworkList + resourceSpecT = orcv1alpha1.ShareNetworkResourceSpec + filterT = orcv1alpha1.ShareNetworkFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = sharenetworkAdapter +) + +type sharenetworkAdapter struct { + *orcv1alpha1.ShareNetwork +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.ShareNetwork +} + +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/sharenetwork/zz_generated.controller.go b/internal/controllers/sharenetwork/zz_generated.controller.go new file mode 100644 index 000000000..4ae4fdb2e --- /dev/null +++ b/internal/controllers/sharenetwork/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 sharenetwork + +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 766500c8f..5ee7aa5da 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -59,6 +59,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=sharenetwork.go -source=../sharenetwork.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ShareNetworkClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt sharenetwork.go > _sharenetwork.go && mv _sharenetwork.go sharenetwork.go" + //go:generate mockgen -package mock -destination=user.go -source=../user.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock UserClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt user.go > _user.go && mv _user.go user.go" diff --git a/internal/osclients/mock/sharenetwork.go b/internal/osclients/mock/sharenetwork.go new file mode 100644 index 000000000..e492bf9d7 --- /dev/null +++ b/internal/osclients/mock/sharenetwork.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: ../sharenetwork.go +// +// Generated by this command: +// +// mockgen -package mock -destination=sharenetwork.go -source=../sharenetwork.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ShareNetworkClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + sharenetworks "github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/sharenetworks" + gomock "go.uber.org/mock/gomock" +) + +// MockShareNetworkClient is a mock of ShareNetworkClient interface. +type MockShareNetworkClient struct { + ctrl *gomock.Controller + recorder *MockShareNetworkClientMockRecorder + isgomock struct{} +} + +// MockShareNetworkClientMockRecorder is the mock recorder for MockShareNetworkClient. +type MockShareNetworkClientMockRecorder struct { + mock *MockShareNetworkClient +} + +// NewMockShareNetworkClient creates a new mock instance. +func NewMockShareNetworkClient(ctrl *gomock.Controller) *MockShareNetworkClient { + mock := &MockShareNetworkClient{ctrl: ctrl} + mock.recorder = &MockShareNetworkClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockShareNetworkClient) EXPECT() *MockShareNetworkClientMockRecorder { + return m.recorder +} + +// CreateShareNetwork mocks base method. +func (m *MockShareNetworkClient) CreateShareNetwork(ctx context.Context, opts sharenetworks.CreateOptsBuilder) (*sharenetworks.ShareNetwork, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateShareNetwork", ctx, opts) + ret0, _ := ret[0].(*sharenetworks.ShareNetwork) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateShareNetwork indicates an expected call of CreateShareNetwork. +func (mr *MockShareNetworkClientMockRecorder) CreateShareNetwork(ctx, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateShareNetwork", reflect.TypeOf((*MockShareNetworkClient)(nil).CreateShareNetwork), ctx, opts) +} + +// DeleteShareNetwork mocks base method. +func (m *MockShareNetworkClient) DeleteShareNetwork(ctx context.Context, resourceID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteShareNetwork", ctx, resourceID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteShareNetwork indicates an expected call of DeleteShareNetwork. +func (mr *MockShareNetworkClientMockRecorder) DeleteShareNetwork(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteShareNetwork", reflect.TypeOf((*MockShareNetworkClient)(nil).DeleteShareNetwork), ctx, resourceID) +} + +// GetShareNetwork mocks base method. +func (m *MockShareNetworkClient) GetShareNetwork(ctx context.Context, resourceID string) (*sharenetworks.ShareNetwork, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetShareNetwork", ctx, resourceID) + ret0, _ := ret[0].(*sharenetworks.ShareNetwork) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetShareNetwork indicates an expected call of GetShareNetwork. +func (mr *MockShareNetworkClientMockRecorder) GetShareNetwork(ctx, resourceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShareNetwork", reflect.TypeOf((*MockShareNetworkClient)(nil).GetShareNetwork), ctx, resourceID) +} + +// ListShareNetworks mocks base method. +func (m *MockShareNetworkClient) ListShareNetworks(ctx context.Context, listOpts sharenetworks.ListOptsBuilder) iter.Seq2[*sharenetworks.ShareNetwork, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListShareNetworks", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*sharenetworks.ShareNetwork, error]) + return ret0 +} + +// ListShareNetworks indicates an expected call of ListShareNetworks. +func (mr *MockShareNetworkClientMockRecorder) ListShareNetworks(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListShareNetworks", reflect.TypeOf((*MockShareNetworkClient)(nil).ListShareNetworks), ctx, listOpts) +} + +// UpdateShareNetwork mocks base method. +func (m *MockShareNetworkClient) UpdateShareNetwork(ctx context.Context, id string, opts sharenetworks.UpdateOptsBuilder) (*sharenetworks.ShareNetwork, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateShareNetwork", ctx, id, opts) + ret0, _ := ret[0].(*sharenetworks.ShareNetwork) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateShareNetwork indicates an expected call of UpdateShareNetwork. +func (mr *MockShareNetworkClientMockRecorder) UpdateShareNetwork(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateShareNetwork", reflect.TypeOf((*MockShareNetworkClient)(nil).UpdateShareNetwork), ctx, id, opts) +} diff --git a/internal/osclients/sharenetwork.go b/internal/osclients/sharenetwork.go index dcc8b008e..a8382eac1 100644 --- a/internal/osclients/sharenetwork.go +++ b/internal/osclients/sharenetwork.go @@ -39,7 +39,7 @@ type sharenetworkClient struct{ client *gophercloud.ServiceClient } // NewShareNetworkClient returns a new OpenStack client. func NewShareNetworkClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (ShareNetworkClient, error) { - client, err := openstack.NewSharedFilesystemV2(providerClient, gophercloud.EndpointOpts{ + client, err := openstack.NewSharedFileSystemV2(providerClient, gophercloud.EndpointOpts{ Region: providerClientOpts.RegionName, Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), }) @@ -52,7 +52,7 @@ func NewShareNetworkClient(providerClient *gophercloud.ProviderClient, providerC } func (c sharenetworkClient) ListShareNetworks(ctx context.Context, listOpts sharenetworks.ListOptsBuilder) iter.Seq2[*sharenetworks.ShareNetwork, error] { - pager := sharenetworks.List(c.client, listOpts) + pager := sharenetworks.ListDetail(c.client, listOpts) return func(yield func(*sharenetworks.ShareNetwork, error) bool) { _ = pager.EachPage(ctx, yieldPage(sharenetworks.ExtractShareNetworks, yield)) } diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 256fa2e1d..8ea474b64 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -49,6 +49,7 @@ type MockScopeFactory struct { UserClient *mock.MockUserClient VolumeClient *mock.MockVolumeClient VolumeTypeClient *mock.MockVolumeTypeClient + ShareNetworkClient *mock.MockShareNetworkClient clientScopeCreateError error } @@ -67,6 +68,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { roleClient := mock.NewMockRoleClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) userClient := mock.NewMockUserClient(mockCtrl) + sharenetworkClient := mock.NewMockShareNetworkClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) volumetypeClient := mock.NewMockVolumeTypeClient(mockCtrl) @@ -83,6 +85,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { NetworkClient: networkClient, RoleClient: roleClient, ServiceClient: serviceClient, + ShareNetworkClient: sharenetworkClient, UserClient: userClient, VolumeClient: volumeClient, VolumeTypeClient: volumetypeClient, @@ -140,6 +143,10 @@ func (f *MockScopeFactory) NewServiceClient() (osclients.ServiceClient, error) { return f.ServiceClient, nil } +func (f *MockScopeFactory) NewShareNetworkClient() (osclients.ShareNetworkClient, error) { + return f.ShareNetworkClient, nil +} + func (f *MockScopeFactory) NewKeyPairClient() (osclients.KeyPairClient, error) { return f.KeyPairClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index aadd5c5ff..1606e18a1 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -185,6 +185,10 @@ func (s *providerScope) NewEndpointClient() (clients.EndpointClient, error) { return clients.NewEndpointClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewShareNetworkClient() (clients.ShareNetworkClient, error) { + return clients.NewShareNetworkClient(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 d8426fd62..0b02b79bd 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -60,6 +60,7 @@ type Scope interface { NewNetworkClient() (osclients.NetworkClient, error) NewRoleClient() (osclients.RoleClient, error) NewServiceClient() (osclients.ServiceClient, error) + NewShareNetworkClient() (osclients.ShareNetworkClient, error) NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) NewVolumeTypeClient() (osclients.VolumeTypeClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d58526493..71fc135ed 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -21,6 +21,7 @@ testDirs: - ./internal/controllers/server/tests/ - ./internal/controllers/servergroup/tests/ - ./internal/controllers/service/tests/ +- ./internal/controllers/sharenetwork/tests/ - ./internal/controllers/subnet/tests/ - ./internal/controllers/trunk/tests/ - ./internal/controllers/user/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetwork.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetwork.go new file mode 100644 index 000000000..8fcacbb0b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetwork.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" +) + +// ShareNetworkApplyConfiguration represents a declarative configuration of the ShareNetwork type for use +// with apply. +type ShareNetworkApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ShareNetworkSpecApplyConfiguration `json:"spec,omitempty"` + Status *ShareNetworkStatusApplyConfiguration `json:"status,omitempty"` +} + +// ShareNetwork constructs a declarative configuration of the ShareNetwork type for use with +// apply. +func ShareNetwork(name, namespace string) *ShareNetworkApplyConfiguration { + b := &ShareNetworkApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("ShareNetwork") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractShareNetwork extracts the applied configuration owned by fieldManager from +// shareNetwork. If no managedFields are found in shareNetwork for fieldManager, a +// ShareNetworkApplyConfiguration 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. +// shareNetwork must be a unmodified ShareNetwork API object that was retrieved from the Kubernetes API. +// ExtractShareNetwork 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 ExtractShareNetwork(shareNetwork *apiv1alpha1.ShareNetwork, fieldManager string) (*ShareNetworkApplyConfiguration, error) { + return extractShareNetwork(shareNetwork, fieldManager, "") +} + +// ExtractShareNetworkStatus is the same as ExtractShareNetwork except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractShareNetworkStatus(shareNetwork *apiv1alpha1.ShareNetwork, fieldManager string) (*ShareNetworkApplyConfiguration, error) { + return extractShareNetwork(shareNetwork, fieldManager, "status") +} + +func extractShareNetwork(shareNetwork *apiv1alpha1.ShareNetwork, fieldManager string, subresource string) (*ShareNetworkApplyConfiguration, error) { + b := &ShareNetworkApplyConfiguration{} + err := managedfields.ExtractInto(shareNetwork, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetwork"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(shareNetwork.Name) + b.WithNamespace(shareNetwork.Namespace) + + b.WithKind("ShareNetwork") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b ShareNetworkApplyConfiguration) 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 *ShareNetworkApplyConfiguration) WithKind(value string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithAPIVersion(value string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithName(value string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithGenerateName(value string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithNamespace(value string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithUID(value types.UID) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithResourceVersion(value string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithGeneration(value int64) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithLabels(entries map[string]string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithAnnotations(entries map[string]string) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithFinalizers(values ...string) *ShareNetworkApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ShareNetworkApplyConfiguration) 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 *ShareNetworkApplyConfiguration) WithSpec(value *ShareNetworkSpecApplyConfiguration) *ShareNetworkApplyConfiguration { + 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 *ShareNetworkApplyConfiguration) WithStatus(value *ShareNetworkStatusApplyConfiguration) *ShareNetworkApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ShareNetworkApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ShareNetworkApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ShareNetworkApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ShareNetworkApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkfilter.go new file mode 100644 index 000000000..e15de0ff6 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkfilter.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" +) + +// ShareNetworkFilterApplyConfiguration represents a declarative configuration of the ShareNetworkFilter type for use +// with apply. +type ShareNetworkFilterApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +// ShareNetworkFilterApplyConfiguration constructs a declarative configuration of the ShareNetworkFilter type for use with +// apply. +func ShareNetworkFilter() *ShareNetworkFilterApplyConfiguration { + return &ShareNetworkFilterApplyConfiguration{} +} + +// 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 *ShareNetworkFilterApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *ShareNetworkFilterApplyConfiguration { + 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 *ShareNetworkFilterApplyConfiguration) WithDescription(value string) *ShareNetworkFilterApplyConfiguration { + b.Description = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkimport.go new file mode 100644 index 000000000..5343a304a --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkimport.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 + +// ShareNetworkImportApplyConfiguration represents a declarative configuration of the ShareNetworkImport type for use +// with apply. +type ShareNetworkImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *ShareNetworkFilterApplyConfiguration `json:"filter,omitempty"` +} + +// ShareNetworkImportApplyConfiguration constructs a declarative configuration of the ShareNetworkImport type for use with +// apply. +func ShareNetworkImport() *ShareNetworkImportApplyConfiguration { + return &ShareNetworkImportApplyConfiguration{} +} + +// 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 *ShareNetworkImportApplyConfiguration) WithID(value string) *ShareNetworkImportApplyConfiguration { + 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 *ShareNetworkImportApplyConfiguration) WithFilter(value *ShareNetworkFilterApplyConfiguration) *ShareNetworkImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcespec.go new file mode 100644 index 000000000..1bdaf30f7 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcespec.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" +) + +// ShareNetworkResourceSpecApplyConfiguration represents a declarative configuration of the ShareNetworkResourceSpec type for use +// with apply. +type ShareNetworkResourceSpecApplyConfiguration struct { + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + NetworkRef *apiv1alpha1.KubernetesNameRef `json:"networkRef,omitempty"` + SubnetRef *apiv1alpha1.KubernetesNameRef `json:"subnetRef,omitempty"` +} + +// ShareNetworkResourceSpecApplyConfiguration constructs a declarative configuration of the ShareNetworkResourceSpec type for use with +// apply. +func ShareNetworkResourceSpec() *ShareNetworkResourceSpecApplyConfiguration { + return &ShareNetworkResourceSpecApplyConfiguration{} +} + +// 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 *ShareNetworkResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenStackName) *ShareNetworkResourceSpecApplyConfiguration { + 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 *ShareNetworkResourceSpecApplyConfiguration) WithDescription(value string) *ShareNetworkResourceSpecApplyConfiguration { + b.Description = &value + return b +} + +// WithNetworkRef sets the NetworkRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NetworkRef field is set to the value of the last call. +func (b *ShareNetworkResourceSpecApplyConfiguration) WithNetworkRef(value apiv1alpha1.KubernetesNameRef) *ShareNetworkResourceSpecApplyConfiguration { + b.NetworkRef = &value + return b +} + +// WithSubnetRef sets the SubnetRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SubnetRef field is set to the value of the last call. +func (b *ShareNetworkResourceSpecApplyConfiguration) WithSubnetRef(value apiv1alpha1.KubernetesNameRef) *ShareNetworkResourceSpecApplyConfiguration { + b.SubnetRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcestatus.go new file mode 100644 index 000000000..9ffc47942 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkresourcestatus.go @@ -0,0 +1,133 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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" +) + +// ShareNetworkResourceStatusApplyConfiguration represents a declarative configuration of the ShareNetworkResourceStatus type for use +// with apply. +type ShareNetworkResourceStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + NeutronNetID *string `json:"neutronNetID,omitempty"` + NeutronSubnetID *string `json:"neutronSubnetID,omitempty"` + NetworkType *string `json:"networkType,omitempty"` + SegmentationID *int32 `json:"segmentationID,omitempty"` + CIDR *string `json:"cidr,omitempty"` + IPVersion *int32 `json:"ipVersion,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + CreatedAt *v1.Time `json:"createdAt,omitempty"` + UpdatedAt *v1.Time `json:"updatedAt,omitempty"` +} + +// ShareNetworkResourceStatusApplyConfiguration constructs a declarative configuration of the ShareNetworkResourceStatus type for use with +// apply. +func ShareNetworkResourceStatus() *ShareNetworkResourceStatusApplyConfiguration { + return &ShareNetworkResourceStatusApplyConfiguration{} +} + +// 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 *ShareNetworkResourceStatusApplyConfiguration) WithName(value string) *ShareNetworkResourceStatusApplyConfiguration { + 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 *ShareNetworkResourceStatusApplyConfiguration) WithDescription(value string) *ShareNetworkResourceStatusApplyConfiguration { + b.Description = &value + return b +} + +// WithNeutronNetID sets the NeutronNetID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NeutronNetID field is set to the value of the last call. +func (b *ShareNetworkResourceStatusApplyConfiguration) WithNeutronNetID(value string) *ShareNetworkResourceStatusApplyConfiguration { + b.NeutronNetID = &value + return b +} + +// WithNeutronSubnetID sets the NeutronSubnetID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NeutronSubnetID field is set to the value of the last call. +func (b *ShareNetworkResourceStatusApplyConfiguration) WithNeutronSubnetID(value string) *ShareNetworkResourceStatusApplyConfiguration { + b.NeutronSubnetID = &value + return b +} + +// WithNetworkType sets the NetworkType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NetworkType field is set to the value of the last call. +func (b *ShareNetworkResourceStatusApplyConfiguration) WithNetworkType(value string) *ShareNetworkResourceStatusApplyConfiguration { + b.NetworkType = &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 *ShareNetworkResourceStatusApplyConfiguration) WithSegmentationID(value int32) *ShareNetworkResourceStatusApplyConfiguration { + b.SegmentationID = &value + return b +} + +// WithCIDR sets the CIDR field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CIDR field is set to the value of the last call. +func (b *ShareNetworkResourceStatusApplyConfiguration) WithCIDR(value string) *ShareNetworkResourceStatusApplyConfiguration { + b.CIDR = &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 *ShareNetworkResourceStatusApplyConfiguration) WithIPVersion(value int32) *ShareNetworkResourceStatusApplyConfiguration { + b.IPVersion = &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 *ShareNetworkResourceStatusApplyConfiguration) WithProjectID(value string) *ShareNetworkResourceStatusApplyConfiguration { + b.ProjectID = &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. +func (b *ShareNetworkResourceStatusApplyConfiguration) WithCreatedAt(value v1.Time) *ShareNetworkResourceStatusApplyConfiguration { + b.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 *ShareNetworkResourceStatusApplyConfiguration) WithUpdatedAt(value v1.Time) *ShareNetworkResourceStatusApplyConfiguration { + b.UpdatedAt = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go new file mode 100644 index 000000000..0e271cc7d --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.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" +) + +// ShareNetworkSpecApplyConfiguration represents a declarative configuration of the ShareNetworkSpec type for use +// with apply. +type ShareNetworkSpecApplyConfiguration struct { + Import *ShareNetworkImportApplyConfiguration `json:"import,omitempty"` + Resource *ShareNetworkResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// ShareNetworkSpecApplyConfiguration constructs a declarative configuration of the ShareNetworkSpec type for use with +// apply. +func ShareNetworkSpec() *ShareNetworkSpecApplyConfiguration { + return &ShareNetworkSpecApplyConfiguration{} +} + +// 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 *ShareNetworkSpecApplyConfiguration) WithImport(value *ShareNetworkImportApplyConfiguration) *ShareNetworkSpecApplyConfiguration { + 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 *ShareNetworkSpecApplyConfiguration) WithResource(value *ShareNetworkResourceSpecApplyConfiguration) *ShareNetworkSpecApplyConfiguration { + 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 *ShareNetworkSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *ShareNetworkSpecApplyConfiguration { + 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 *ShareNetworkSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *ShareNetworkSpecApplyConfiguration { + 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 *ShareNetworkSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *ShareNetworkSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go new file mode 100644 index 000000000..335bcfb77 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.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" +) + +// ShareNetworkStatusApplyConfiguration represents a declarative configuration of the ShareNetworkStatus type for use +// with apply. +type ShareNetworkStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ShareNetworkResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// ShareNetworkStatusApplyConfiguration constructs a declarative configuration of the ShareNetworkStatus type for use with +// apply. +func ShareNetworkStatus() *ShareNetworkStatusApplyConfiguration { + return &ShareNetworkStatusApplyConfiguration{} +} + +// 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 *ShareNetworkStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *ShareNetworkStatusApplyConfiguration { + 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 *ShareNetworkStatusApplyConfiguration) WithID(value string) *ShareNetworkStatusApplyConfiguration { + 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 *ShareNetworkStatusApplyConfiguration) WithResource(value *ShareNetworkResourceStatusApplyConfiguration) *ShareNetworkStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index b51e2a458..45321b551 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -3141,6 +3141,133 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServiceResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetwork + 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.ShareNetworkSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkFilter + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkResourceSpec + map: + fields: + - name: description + type: + scalar: string + - name: name + type: + scalar: string + - name: networkRef + type: + scalar: string + - name: subnetRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkResourceStatus + map: + fields: + - name: cidr + type: + scalar: string + default: "" + - name: createdAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: description + type: + scalar: string + - name: ipVersion + type: + scalar: numeric + - name: name + type: + scalar: string + - name: networkType + type: + scalar: string + - name: neutronNetID + type: + scalar: string + - name: neutronSubnetID + type: + scalar: string + - name: projectID + type: + scalar: string + - name: segmentationID + type: + scalar: numeric + - name: updatedAt + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkSpec + 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.ShareNetworkImport + - 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.ShareNetworkResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkStatus + 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.ShareNetworkResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Subnet map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 73e66b28b..5d8f68cd9 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -380,6 +380,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ServiceSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServiceStatus"): return &apiv1alpha1.ServiceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetwork"): + return &apiv1alpha1.ShareNetworkApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetworkFilter"): + return &apiv1alpha1.ShareNetworkFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetworkImport"): + return &apiv1alpha1.ShareNetworkImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetworkResourceSpec"): + return &apiv1alpha1.ShareNetworkResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetworkResourceStatus"): + return &apiv1alpha1.ShareNetworkResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetworkSpec"): + return &apiv1alpha1.ShareNetworkSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ShareNetworkStatus"): + return &apiv1alpha1.ShareNetworkStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Subnet"): return &apiv1alpha1.SubnetApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("SubnetFilter"): 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 a145cab6f..d5c517b1e 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 { ServersGetter ServerGroupsGetter ServicesGetter + ShareNetworksGetter SubnetsGetter TrunksGetter UsersGetter @@ -135,6 +136,10 @@ func (c *OpenstackV1alpha1Client) Services(namespace string) ServiceInterface { return newServices(c, namespace) } +func (c *OpenstackV1alpha1Client) ShareNetworks(namespace string) ShareNetworkInterface { + return newShareNetworks(c, namespace) +} + func (c *OpenstackV1alpha1Client) Subnets(namespace string) SubnetInterface { return newSubnets(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 87bc2b39d..f5dcb5da4 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) Services(namespace string) v1alpha1.ServiceInter return newFakeServices(c, namespace) } +func (c *FakeOpenstackV1alpha1) ShareNetworks(namespace string) v1alpha1.ShareNetworkInterface { + return newFakeShareNetworks(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Subnets(namespace string) v1alpha1.SubnetInterface { return newFakeSubnets(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_sharenetwork.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_sharenetwork.go new file mode 100644 index 000000000..2e1081d9f --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_sharenetwork.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" +) + +// fakeShareNetworks implements ShareNetworkInterface +type fakeShareNetworks struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.ShareNetwork, *v1alpha1.ShareNetworkList, *apiv1alpha1.ShareNetworkApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeShareNetworks(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.ShareNetworkInterface { + return &fakeShareNetworks{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.ShareNetwork, *v1alpha1.ShareNetworkList, *apiv1alpha1.ShareNetworkApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("sharenetworks"), + v1alpha1.SchemeGroupVersion.WithKind("ShareNetwork"), + func() *v1alpha1.ShareNetwork { return &v1alpha1.ShareNetwork{} }, + func() *v1alpha1.ShareNetworkList { return &v1alpha1.ShareNetworkList{} }, + func(dst, src *v1alpha1.ShareNetworkList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.ShareNetworkList) []*v1alpha1.ShareNetwork { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.ShareNetworkList, items []*v1alpha1.ShareNetwork) { + 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 de60388f2..e13858a9c 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 ServerGroupExpansion interface{} type ServiceExpansion interface{} +type ShareNetworkExpansion interface{} + type SubnetExpansion interface{} type TrunkExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/sharenetwork.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/sharenetwork.go new file mode 100644 index 000000000..1c64adfc7 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/sharenetwork.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" +) + +// ShareNetworksGetter has a method to return a ShareNetworkInterface. +// A group's client should implement this interface. +type ShareNetworksGetter interface { + ShareNetworks(namespace string) ShareNetworkInterface +} + +// ShareNetworkInterface has methods to work with ShareNetwork resources. +type ShareNetworkInterface interface { + Create(ctx context.Context, shareNetwork *apiv1alpha1.ShareNetwork, opts v1.CreateOptions) (*apiv1alpha1.ShareNetwork, error) + Update(ctx context.Context, shareNetwork *apiv1alpha1.ShareNetwork, opts v1.UpdateOptions) (*apiv1alpha1.ShareNetwork, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, shareNetwork *apiv1alpha1.ShareNetwork, opts v1.UpdateOptions) (*apiv1alpha1.ShareNetwork, 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.ShareNetwork, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.ShareNetworkList, 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.ShareNetwork, err error) + Apply(ctx context.Context, shareNetwork *applyconfigurationapiv1alpha1.ShareNetworkApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.ShareNetwork, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, shareNetwork *applyconfigurationapiv1alpha1.ShareNetworkApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.ShareNetwork, err error) + ShareNetworkExpansion +} + +// shareNetworks implements ShareNetworkInterface +type shareNetworks struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.ShareNetwork, *apiv1alpha1.ShareNetworkList, *applyconfigurationapiv1alpha1.ShareNetworkApplyConfiguration] +} + +// newShareNetworks returns a ShareNetworks +func newShareNetworks(c *OpenstackV1alpha1Client, namespace string) *shareNetworks { + return &shareNetworks{ + gentype.NewClientWithListAndApply[*apiv1alpha1.ShareNetwork, *apiv1alpha1.ShareNetworkList, *applyconfigurationapiv1alpha1.ShareNetworkApplyConfiguration]( + "sharenetworks", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.ShareNetwork { return &apiv1alpha1.ShareNetwork{} }, + func() *apiv1alpha1.ShareNetworkList { return &apiv1alpha1.ShareNetworkList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index b9b415243..2e9f92392 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 { ServerGroups() ServerGroupInformer // Services returns a ServiceInformer. Services() ServiceInformer + // ShareNetworks returns a ShareNetworkInformer. + ShareNetworks() ShareNetworkInformer // Subnets returns a SubnetInformer. Subnets() SubnetInformer // Trunks returns a TrunkInformer. @@ -180,6 +182,11 @@ func (v *version) Services() ServiceInformer { return &serviceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// ShareNetworks returns a ShareNetworkInformer. +func (v *version) ShareNetworks() ShareNetworkInformer { + return &shareNetworkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Subnets returns a SubnetInformer. func (v *version) Subnets() SubnetInformer { return &subnetInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/sharenetwork.go b/pkg/clients/informers/externalversions/api/v1alpha1/sharenetwork.go new file mode 100644 index 000000000..1b9c5c3d2 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/sharenetwork.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" +) + +// ShareNetworkInformer provides access to a shared informer and lister for +// ShareNetworks. +type ShareNetworkInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.ShareNetworkLister +} + +type shareNetworkInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewShareNetworkInformer constructs a new informer for ShareNetwork 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 NewShareNetworkInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredShareNetworkInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredShareNetworkInformer constructs a new informer for ShareNetwork 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 NewFilteredShareNetworkInformer(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().ShareNetworks(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().ShareNetworks(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().ShareNetworks(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().ShareNetworks(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.ShareNetwork{}, + resyncPeriod, + indexers, + ) +} + +func (f *shareNetworkInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredShareNetworkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *shareNetworkInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.ShareNetwork{}, f.defaultInformer) +} + +func (f *shareNetworkInformer) Lister() apiv1alpha1.ShareNetworkLister { + return apiv1alpha1.NewShareNetworkLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 99f589164..fb3637f1e 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().ServerGroups().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("services"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Services().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("sharenetworks"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().ShareNetworks().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("subnets"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Subnets().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("trunks"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 98590bb01..4d7043581 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -170,6 +170,14 @@ type ServiceListerExpansion interface{} // ServiceNamespaceLister. type ServiceNamespaceListerExpansion interface{} +// ShareNetworkListerExpansion allows custom methods to be added to +// ShareNetworkLister. +type ShareNetworkListerExpansion interface{} + +// ShareNetworkNamespaceListerExpansion allows custom methods to be added to +// ShareNetworkNamespaceLister. +type ShareNetworkNamespaceListerExpansion interface{} + // SubnetListerExpansion allows custom methods to be added to // SubnetLister. type SubnetListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/sharenetwork.go b/pkg/clients/listers/api/v1alpha1/sharenetwork.go new file mode 100644 index 000000000..15c506d75 --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/sharenetwork.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" +) + +// ShareNetworkLister helps list ShareNetworks. +// All objects returned here must be treated as read-only. +type ShareNetworkLister interface { + // List lists all ShareNetworks in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.ShareNetwork, err error) + // ShareNetworks returns an object that can list and get ShareNetworks. + ShareNetworks(namespace string) ShareNetworkNamespaceLister + ShareNetworkListerExpansion +} + +// shareNetworkLister implements the ShareNetworkLister interface. +type shareNetworkLister struct { + listers.ResourceIndexer[*apiv1alpha1.ShareNetwork] +} + +// NewShareNetworkLister returns a new ShareNetworkLister. +func NewShareNetworkLister(indexer cache.Indexer) ShareNetworkLister { + return &shareNetworkLister{listers.New[*apiv1alpha1.ShareNetwork](indexer, apiv1alpha1.Resource("sharenetwork"))} +} + +// ShareNetworks returns an object that can list and get ShareNetworks. +func (s *shareNetworkLister) ShareNetworks(namespace string) ShareNetworkNamespaceLister { + return shareNetworkNamespaceLister{listers.NewNamespaced[*apiv1alpha1.ShareNetwork](s.ResourceIndexer, namespace)} +} + +// ShareNetworkNamespaceLister helps list and get ShareNetworks. +// All objects returned here must be treated as read-only. +type ShareNetworkNamespaceLister interface { + // List lists all ShareNetworks in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.ShareNetwork, err error) + // Get retrieves the ShareNetwork from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.ShareNetwork, error) + ShareNetworkNamespaceListerExpansion +} + +// shareNetworkNamespaceLister implements the ShareNetworkNamespaceLister +// interface. +type shareNetworkNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.ShareNetwork] +} diff --git a/test/apivalidations/sharenetwork_test.go b/test/apivalidations/sharenetwork_test.go new file mode 100644 index 000000000..e0bd45968 --- /dev/null +++ b/test/apivalidations/sharenetwork_test.go @@ -0,0 +1,124 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 ( + shareNetworkName = "sharenetwork-foo" + shareNetworkID = "7b7a8e4c-1c2d-4e5f-9a8b-3c4d5e6f7a8b" +) + +func shareNetworkStub(namespace *corev1.Namespace) *orcv1alpha1.ShareNetwork { + obj := &orcv1alpha1.ShareNetwork{} + obj.Name = shareNetworkName + obj.Namespace = namespace.Name + return obj +} + +func testShareNetworkResource() *applyconfigv1alpha1.ShareNetworkResourceSpecApplyConfiguration { + return applyconfigv1alpha1.ShareNetworkResourceSpec() +} + +func baseShareNetworkPatch(shareNetwork client.Object) *applyconfigv1alpha1.ShareNetworkApplyConfiguration { + return applyconfigv1alpha1.ShareNetwork(shareNetwork.GetName(), shareNetwork.GetNamespace()). + WithSpec(applyconfigv1alpha1.ShareNetworkSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +var _ = Describe("ORC ShareNetwork API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + It("should allow to create a minimal share network and managementPolicy should default to managed", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec.WithResource(testShareNetworkResource()) + Expect(applyObj(ctx, shareNetwork, patch)).To(Succeed()) + Expect(shareNetwork.Spec.ManagementPolicy).To(Equal(orcv1alpha1.ManagementPolicyManaged)) + }) + + It("should not permit empty import filter", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ShareNetworkImport(). + WithFilter(applyconfigv1alpha1.ShareNetworkFilter())) + Expect(applyObj(ctx, shareNetwork, patch)).NotTo(Succeed()) + }) + + It("should permit valid import filter", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec. + WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged). + WithImport(applyconfigv1alpha1.ShareNetworkImport(). + WithFilter(applyconfigv1alpha1.ShareNetworkFilter().WithName("foo").WithDescription("bar"))) + Expect(applyObj(ctx, shareNetwork, patch)).To(Succeed()) + }) + + // NetworkRef and SubnetRef co-dependency validation tests + Describe("networkRef and subnetRef validation", func() { + It("should allow both networkRef and subnetRef together", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec.WithResource( + applyconfigv1alpha1.ShareNetworkResourceSpec(). + WithNetworkRef("foo"). + WithSubnetRef("bar")) + Expect(applyObj(ctx, shareNetwork, patch)).To(Succeed(), "should accept both networkRef and subnetRef") + }) + + It("should allow neither networkRef nor subnetRef", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec.WithResource(testShareNetworkResource()) + Expect(applyObj(ctx, shareNetwork, patch)).To(Succeed(), "should accept when both are absent") + }) + + It("should reject networkRef without subnetRef", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec.WithResource( + applyconfigv1alpha1.ShareNetworkResourceSpec(). + WithNetworkRef("foo")) + Expect(applyObj(ctx, shareNetwork, patch)).NotTo(Succeed(), "should reject networkRef without subnetRef") + }) + + It("should reject subnetRef without networkRef", func(ctx context.Context) { + shareNetwork := shareNetworkStub(namespace) + patch := baseShareNetworkPatch(shareNetwork) + patch.Spec.WithResource( + applyconfigv1alpha1.ShareNetworkResourceSpec(). + WithSubnetRef("bar")) + Expect(applyObj(ctx, shareNetwork, patch)).NotTo(Succeed(), "should reject subnetRef without networkRef") + }) + }) +}) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 8bbac5ae3..1da353f71 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 - [Server](#server) - [ServerGroup](#servergroup) - [Service](#service) +- [ShareNetwork](#sharenetwork) - [Subnet](#subnet) - [Trunk](#trunk) - [User](#user) @@ -519,6 +520,7 @@ _Appears in:_ - [ServerGroupSpec](#servergroupspec) - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) +- [ShareNetworkSpec](#sharenetworkspec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) @@ -2239,6 +2241,7 @@ _Appears in:_ - [ServerGroupSpec](#servergroupspec) - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) +- [ShareNetworkSpec](#sharenetworkspec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) @@ -2278,6 +2281,7 @@ _Appears in:_ - [ServerGroupSpec](#servergroupspec) - [ServerSpec](#serverspec) - [ServiceSpec](#servicespec) +- [ShareNetworkSpec](#sharenetworkspec) - [SubnetSpec](#subnetspec) - [TrunkSpec](#trunkspec) - [UserSpec](#userspec) @@ -4257,12 +4261,146 @@ _Appears in:_ | `resource` _[ServiceResourceStatus](#serviceresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +#### ShareNetwork +ShareNetwork is the Schema for an ORC resource. + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `ShareNetwork` | | | +| `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` _[ShareNetworkSpec](#sharenetworkspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[ShareNetworkStatus](#sharenetworkstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### ShareNetworkFilter + + + +ShareNetworkFilter defines an existing resource by its properties + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [ShareNetworkImport](#sharenetworkimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name of the existing resource | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description of the existing resource | | MaxLength: 255
MinLength: 1
Optional: \{\}
| + + +#### ShareNetworkImport + + + +ShareNetworkImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [ShareNetworkSpec](#sharenetworkspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
MaxLength: 36
Optional: \{\}
| +| `filter` _[ShareNetworkFilter](#sharenetworkfilter)_ | 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: \{\}
| + + +#### ShareNetworkResourceSpec + + + +ShareNetworkResourceSpec contains the desired state of the resource. + + + +_Appears in:_ +- [ShareNetworkSpec](#sharenetworkspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 255
MinLength: 1
Optional: \{\}
| +| `networkRef` _[KubernetesNameRef](#kubernetesnameref)_ | networkRef is a reference to the ORC Network which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `subnetRef` _[KubernetesNameRef](#kubernetesnameref)_ | subnetRef is a reference to the ORC Subnet which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### ShareNetworkResourceStatus + + + +ShareNetworkResourceStatus represents the observed state of the resource. + + + +_Appears in:_ +- [ShareNetworkStatus](#sharenetworkstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is a Human-readable name for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `description` _string_ | description is a human-readable description for the resource. | | MaxLength: 1024
Optional: \{\}
| +| `neutronNetID` _string_ | neutronNetID is the Neutron network ID. | | MaxLength: 1024
Optional: \{\}
| +| `neutronSubnetID` _string_ | neutronSubnetID is the Neutron subnet ID. | | MaxLength: 1024
Optional: \{\}
| +| `networkType` _string_ | networkType is the network type (e.g., vlan, vxlan, flat). | | MaxLength: 1024
Optional: \{\}
| +| `segmentationID` _integer_ | segmentationID is the segmentation ID of the network. | | Optional: \{\}
| +| `cidr` _string_ | cidr is the CIDR of the subnet. | | MaxLength: 1024
Optional: \{\}
| +| `ipVersion` _integer_ | ipVersion is the IP version (4 or 6). | | Optional: \{\}
| +| `projectID` _string_ | projectID is the ID of the project that owns the share network. | | 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. | | 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. | | Optional: \{\}
| + + +#### ShareNetworkSpec + + + +ShareNetworkSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [ShareNetwork](#sharenetwork) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[ShareNetworkImport](#sharenetworkimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[ShareNetworkResourceSpec](#sharenetworkresourcespec)_ | 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: \{\}
| + + +#### ShareNetworkStatus + + + +ShareNetworkStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [ShareNetwork](#sharenetwork) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| +| `resource` _[ShareNetworkResourceStatus](#sharenetworkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| + + #### Subnet From a1f791d916fe4d1370492354fde30c9a89ec0968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Mon, 8 Jun 2026 15:37:07 +0200 Subject: [PATCH 195/237] Bump go to v1.25.11 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c29e8d467..ea8d927de 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.69.3 -GO_VERSION ?= 1.25.10 +GO_VERSION ?= 1.25.11 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) From ee7e2a39ec8b74cfc4484a6db92fb77829efe1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 9 Jun 2026 09:22:58 +0200 Subject: [PATCH 196/237] ci: verify generate-bundle in CI Add a step to the generate workflow to run 'make generate-bundle' and verify the result is committed. This ensures that config/manifests/bases/orc.clusterserviceversion.yaml does not drift when new CRDs are added. Update the CSV base to include AddressScope, ApplicationCredential, and User CRDs which were added without running generate-bundle. --- .github/workflows/generate.yaml | 6 ++++++ .../bases/orc.clusterserviceversion.yaml | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index c5b20503b..935fe6e9f 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -20,3 +20,9 @@ jobs: - run: | make generate git diff --exit-code + + - run: sudo apt-get install -y libgpgme-dev + + - run: | + make generate-bundle + git diff --exit-code diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 742c646bc..89421fc32 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -19,6 +19,16 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: AddressScope is the Schema for an ORC resource. + displayName: Address Scope + kind: AddressScope + name: addressscopes.openstack.k-orc.cloud + version: v1alpha1 + - description: ApplicationCredential is the Schema for an ORC resource. + displayName: Application Credential + kind: ApplicationCredential + name: applicationcredentials.openstack.k-orc.cloud + version: v1alpha1 - description: Domain is the Schema for an ORC resource. displayName: Domain kind: Domain @@ -119,6 +129,11 @@ spec: kind: Trunk name: trunks.openstack.k-orc.cloud version: v1alpha1 + - description: User is the Schema for an ORC resource. + displayName: User + kind: User + name: users.openstack.k-orc.cloud + version: v1alpha1 - description: Volume is the Schema for an ORC resource. displayName: Volume kind: Volume From 4dd91d0bc5d5da841a46497a07af33efa5fbb21b Mon Sep 17 00:00:00 2001 From: Fabian Mutzbauer Date: Tue, 9 Jun 2026 14:58:04 +0200 Subject: [PATCH 197/237] flavor: add id field for creation --- api/v1alpha1/flavor_types.go | 8 ++++++++ cmd/models-schema/zz_generated.openapi.go | 7 +++++++ .../bases/openstack.k-orc.cloud_flavors.yaml | 8 ++++++++ internal/controllers/flavor/actuator.go | 1 + .../tests/flavor-create-full/00-assert.yaml | 3 ++- .../flavor-create-full/00-create-resource.yaml | 1 + .../api/v1alpha1/flavorresourcespec.go | 9 +++++++++ .../applyconfiguration/internal/internal.go | 3 +++ test/apivalidations/flavor_test.go | 17 ++++++++++++++++- website/docs/crd-reference.md | 1 + 10 files changed, 56 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/flavor_types.go b/api/v1alpha1/flavor_types.go index 991133619..4ebbeccf9 100644 --- a/api/v1alpha1/flavor_types.go +++ b/api/v1alpha1/flavor_types.go @@ -24,6 +24,14 @@ type FlavorResourceSpec struct { // +optional Name *OpenStackName `json:"name,omitempty"` + // id will be the id of the created resource. If not specified, a random + // UUID will be generated by OpenStack. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +kubebuilder:validation:Pattern=^[a-zA-Z0-9._-]([a-zA-Z0-9. _-]*[a-zA-Z0-9._-])?$ + // +optional + ID string `json:"id,omitempty"` //nolint:kubeapilinter // intentionally allow raw ID + // description contains a free form description of the flavor. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=65535 diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 8960fadc4..5601a14eb 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -2856,6 +2856,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref Format: "", }, }, + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id will be the id of the created resource. If not specified, a random UUID will be generated by OpenStack.", + Type: []string{"string"}, + Format: "", + }, + }, "description": { SchemaProps: spec.SchemaProps{ Description: "description contains a free form description of the flavor.", diff --git a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml index e2e9c08c6..d85dd72f1 100644 --- a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml @@ -189,6 +189,14 @@ spec: format: int32 minimum: 0 type: integer + id: + description: |- + id will be the id of the created resource. If not specified, a random + UUID will be generated by OpenStack. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9._-]([a-zA-Z0-9. _-]*[a-zA-Z0-9._-])?$ + type: string isPublic: description: isPublic flags a flavor as being available to all projects or not. diff --git a/internal/controllers/flavor/actuator.go b/internal/controllers/flavor/actuator.go index 16eb9c73b..f80146268 100644 --- a/internal/controllers/flavor/actuator.go +++ b/internal/controllers/flavor/actuator.go @@ -152,6 +152,7 @@ func (actuator flavorActuator) CreateResource(ctx context.Context, obj orcObject IsPublic: resource.IsPublic, Ephemeral: ptr.To(int(resource.Ephemeral)), Description: ptr.Deref(resource.Description, ""), + ID: resource.ID, } osResource, err := actuator.osClient.CreateFlavor(ctx, createOpts) diff --git a/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml b/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml index 2074a3ece..dd94b805e 100644 --- a/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml +++ b/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml @@ -4,6 +4,7 @@ kind: Flavor metadata: name: flavor-create-full status: + id: testId-123 resource: name: flavor-create-full-override description: Flavor from "create full" test @@ -12,4 +13,4 @@ status: disk: 20 swap: 2 isPublic: false - ephemeral: 1 + ephemeral: 1 \ No newline at end of file diff --git a/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml b/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml index f705c1af5..470180d72 100644 --- a/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml +++ b/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml @@ -17,3 +17,4 @@ spec: swap: 2 isPublic: false ephemeral: 1 + id: testId-123 diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go index 335f722a6..4fc4161e0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go @@ -26,6 +26,7 @@ import ( // with apply. type FlavorResourceSpecApplyConfiguration struct { Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + ID *string `json:"id,omitempty"` Description *string `json:"description,omitempty"` RAM *int32 `json:"ram,omitempty"` Vcpus *int32 `json:"vcpus,omitempty"` @@ -49,6 +50,14 @@ func (b *FlavorResourceSpecApplyConfiguration) WithName(value apiv1alpha1.OpenSt 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 *FlavorResourceSpecApplyConfiguration) WithID(value string) *FlavorResourceSpecApplyConfiguration { + b.ID = &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. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 45321b551..10461ed9c 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -678,6 +678,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: ephemeral type: scalar: numeric + - name: id + type: + scalar: string - name: isPublic type: scalar: boolean diff --git a/test/apivalidations/flavor_test.go b/test/apivalidations/flavor_test.go index dd30e2f10..847af3a15 100644 --- a/test/apivalidations/flavor_test.go +++ b/test/apivalidations/flavor_test.go @@ -137,7 +137,6 @@ var _ = Describe("ORC Flavor API validations", func() { maxString := strings.Repeat("a", 65536) patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithRAM(1).WithVcpus(1).WithDescription(maxString)) Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.resource.description: Too long"))) - }) It("should reject import filter with value less than minimal", func(ctx context.Context) { @@ -149,4 +148,20 @@ 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 reject flavor IDs which are not according to the specified regex", func(ctx context.Context) { + flavor := flavorStub(namespace) + patch := baseFlavorPatch(flavor) + maxString := strings.Repeat("a", 256) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithID(" test").WithRAM(1).WithVcpus(1).WithDescription("test").WithDisk(1)) + Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.resource.id: Invalid value"))) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithID("test ").WithRAM(1).WithVcpus(1).WithDescription("test").WithDisk(1)) + Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.resource.id: Invalid value"))) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithID(maxString).WithRAM(1).WithVcpus(1).WithDescription("test").WithDisk(1)) + Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.resource.id: Too long"))) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithID("").WithRAM(1).WithVcpus(1).WithDescription("test").WithDisk(1)) + Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("spec.resource.id in body should be at least 1 chars long"))) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithID("test.id -123_").WithRAM(1).WithVcpus(1).WithDescription("test").WithDisk(1)) + Expect(applyObj(ctx, flavor, patch)).To(Succeed()) + }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 1da353f71..de769a2d0 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1019,6 +1019,7 @@ _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: `^[^,]+$`
Optional: \{\}
| +| `id` _string_ | id will be the id of the created resource. If not specified, a random
UUID will be generated by OpenStack. | | MaxLength: 255
MinLength: 1
Pattern: `^[a-zA-Z0-9._-]([a-zA-Z0-9. _-]*[a-zA-Z0-9._-])?$`
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: \{\}
| From 979129607a36ea9bc7b3fbd9d88f9a79fbf5c922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 10 Jun 2026 10:00:07 +0200 Subject: [PATCH 198/237] Update release-notes skills to account for release branch --- .agents/skills/release-notes/SKILL.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.agents/skills/release-notes/SKILL.md b/.agents/skills/release-notes/SKILL.md index a6f6c8a8c..f2afc0076 100644 --- a/.agents/skills/release-notes/SKILL.md +++ b/.agents/skills/release-notes/SKILL.md @@ -20,6 +20,7 @@ Use this skill when: Ask the user for: 1. **New version number** (e.g., `v2.5.0`) 2. **Release date** (default: today) +3. **Release branch** (optional): The branch from which the release will be cut. Defaults to `HEAD`. When the release is cut from a different branch (e.g., `release-2.0`), use that branch as the upper bound of the commit range instead of `HEAD`. Release notes may be written on `main` even though the release is cut from a release branch. Then determine the previous release tag automatically: ```bash @@ -28,25 +29,25 @@ git tag --sort=-v:refname | head -1 ## Step 2: Collect Git History -Run these commands to gather the raw data (replace `` with the previous tag): +Run these commands to gather the raw data. Replace `` with the previous tag and `` with the release branch (e.g., `upstream/release-2.0`) or `HEAD` if releasing from the current branch: ```bash # Full commit log -git log ..HEAD --oneline +git log .. --oneline # Contributors with commit counts -git shortlog -sne ..HEAD +git shortlog -sne .. # New controller directories (compare directory listings) diff <(git ls-tree -d --name-only internal/controllers/ | sort) \ - <(git ls-tree -d --name-only HEAD internal/controllers/ | sort) \ + <(git ls-tree -d --name-only internal/controllers/ | sort) \ | grep '^>' # All authors who ever contributed before this release git log --format='%aN' | sort -u > /tmp/old-contributors.txt # Authors in this release -git log ..HEAD --format='%aN' | sort -u > /tmp/new-contributors.txt +git log .. --format='%aN' | sort -u > /tmp/new-contributors.txt # First-time contributors comm -13 /tmp/old-contributors.txt /tmp/new-contributors.txt @@ -54,10 +55,12 @@ comm -13 /tmp/old-contributors.txt /tmp/new-contributors.txt For each first-time contributor, find the PR number of their first contribution: ```bash -git log ..HEAD --author="" --oneline --reverse | head -1 +git log .. --author="" --oneline --reverse | head -1 ``` Then look up the corresponding PR number from the merge commit message (format: `Merge pull request #NNN`). +> **Note**: When using a release branch, make sure to fetch it first (e.g., `git fetch upstream release-2.0`). The merge commit on the release branch may reference a backport PR number rather than the original PR. Use the original PR number from `main` for release notes since that's where the review and discussion happened. + ## Step 3: Categorize Changes Review every commit and sort into sections. Use these rules: From a1384f18b3bad95aa19bbcaa188530931003ad48 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 9 Jun 2026 18:25:23 +0100 Subject: [PATCH 199/237] Release notes for v2.6.0 Signed-off-by: Stephen Finucane --- website/docs/changelog.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/website/docs/changelog.md b/website/docs/changelog.md index f1f0c985f..ed9a49857 100644 --- a/website/docs/changelog.md +++ b/website/docs/changelog.md @@ -1,5 +1,26 @@ # Changelog +## v2.6 - June 10, 2026 + +### New features + +- Flavor: Added `id` field for creation + +### Bug fixes + +- Tightened adoption filters across multiple controllers (AddressScope, FloatingIP, Group, Network, Port, Project, Router, SecurityGroup, ServerGroup, Subnet, Trunk, User) to prevent adopting resources that don't fully match the spec +- Fixed terminal error classification: use `IsRetryable` instead of `IsConflict` so non-HTTP gophercloud errors (e.g., client-side validation failures) are no longer retried indefinitely (Fixes [#241](https://github.com/k-orc/openstack-resource-controller/issues/241)) +- Treated Neutron quota-exceeded 409 errors as retryable so controllers retry when quota becomes available (Fixes [#667](https://github.com/k-orc/openstack-resource-controller/issues/667)) +- Fixed port status not updating to ACTIVE after server interface attachment +- Fixed volume status not updating to in-use after server attachment + +### Infrastructure improvements + +- Bumped Go to 1.25.11 +- Bumped kuttl to v0.26.0 +- Bumped golang.org/x/net to v0.53.0 and other dependency updates +- Added CI verification for `generate-bundle` + ## v2.5 - April 16, 2026 This release adds five new controllers spanning Neutron and Keystone services, From 97a6cf464eca5ba5b4e7a0ae56c8b633596a77e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 10 Jun 2026 12:12:41 +0200 Subject: [PATCH 200/237] floatingip: fix flaky floatingip-create-full E2E test Add allocationPools to the external subnet to prevent the router gateway port from grabbing the IP address requested by the FloatingIP. Without allocationPools, Neutron auto-allocates from the full subnet range and can non-deterministically assign 192.168.155.5 to the router gateway port, causing the FloatingIP creation to fail with a 409 Conflict. Fixes #812 --- .../tests/floatingip-create-full/00-create-resource.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/controllers/floatingip/tests/floatingip-create-full/00-create-resource.yaml b/internal/controllers/floatingip/tests/floatingip-create-full/00-create-resource.yaml index a8cc7ff44..d81053c9a 100644 --- a/internal/controllers/floatingip/tests/floatingip-create-full/00-create-resource.yaml +++ b/internal/controllers/floatingip/tests/floatingip-create-full/00-create-resource.yaml @@ -35,6 +35,9 @@ spec: networkRef: floatingip-create-full-external ipVersion: 4 cidr: 192.168.155.0/24 + allocationPools: + - start: 192.168.155.100 + end: 192.168.155.200 --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Router From 8d0b885e757e8df534f7eca4af67a21617b85706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 10 Jun 2026 14:28:26 +0200 Subject: [PATCH 201/237] Fix controllers version matrix Share Network controller merged in main, after we branched out v2 and was not backported. It's going to be in the next major version. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 706cce900..a5663e5fd 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ kubectl delete -f $ORC_RELEASE | server | | ◐ | ◐ | | server group | | ✔ | ✔ | | service | | ✔ | ✔ | -| share network | | ◐ | ◐ | +| share network | | | ◐ | | subnet | | ◐ | ◐ | | trunk | | ✔ | ✔ | | user | | ◐ | ◐ | From 2c6a46161bef89aaecb58a7c44f912f99fefa51b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:18:24 +0000 Subject: [PATCH 202/237] :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.2 to 6.0.3 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.3 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/pr-dependabot.yaml | 2 +- .github/workflows/release_image.yaml | 2 +- .github/workflows/semver.yaml | 2 +- .github/workflows/unit.yml | 2 +- .github/workflows/website.yaml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- .github/workflows/zizmor.yaml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index 464be4d23..50792d9dc 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 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 8f9e0220d..d19ccc71e 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index 32d561030..b09d49f91 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # tag=v1.3.0 diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index 935fe6e9f..e568fb9e4 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index 80def4091..a4f352f6b 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index a9b043e38..7f6da21f0 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 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/release_image.yaml b/.github/workflows/release_image.yaml index 559d08217..5fd4e1574 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: # Required for git describe to generate correct output for populating # build variables diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 5bd81168e..b5cff83da 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -13,7 +13,7 @@ jobs: analyze: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 8f53485e3..68224b27c 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -17,7 +17,7 @@ jobs: - '1' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index 81c385abf..d2f94d363 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 703f9332d..2194bd78f 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: ref: ${{ matrix.branch }} persist-credentials: false diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index 5d25c869f..598005ade 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -20,7 +20,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # tag=v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 with: persist-credentials: false From d501ce999686781a1469837b3909f001407bb92b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:14:22 +0000 Subject: [PATCH 203/237] :seedling:(deps): Bump the all-go-mod-patch-and-minor group across 1 directory with 7 updates Bumps the all-go-mod-patch-and-minor group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) | `2.29.0` | `2.31.0` | | [github.com/onsi/gomega](https://github.com/onsi/gomega) | `1.41.0` | `1.42.0` | | [golang.org/x/text](https://github.com/golang/text) | `0.37.0` | `0.38.0` | | [k8s.io/api](https://github.com/kubernetes/api) | `0.34.8` | `0.34.9` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.34.8` | `0.34.9` | | [k8s.io/code-generator](https://github.com/kubernetes/code-generator) | `0.34.8` | `0.34.9` | Updates `github.com/onsi/ginkgo/v2` from 2.29.0 to 2.31.0 - [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.29.0...v2.31.0) Updates `github.com/onsi/gomega` from 1.41.0 to 1.42.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.41.0...v1.42.0) Updates `golang.org/x/text` from 0.37.0 to 0.38.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.37.0...v0.38.0) Updates `k8s.io/api` from 0.34.8 to 0.34.9 - [Commits](https://github.com/kubernetes/api/compare/v0.34.8...v0.34.9) Updates `k8s.io/apimachinery` from 0.34.8 to 0.34.9 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.8...v0.34.9) Updates `k8s.io/client-go` from 0.34.8 to 0.34.9 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.8...v0.34.9) Updates `k8s.io/code-generator` from 0.34.8 to 0.34.9 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.8...v0.34.9) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.31.0 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.42.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.38.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.9 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.9 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.9 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.9 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 | 26 +++++++++++++------------- go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 8839cbb64..2ef833a01 100644 --- a/go.mod +++ b/go.mod @@ -8,15 +8,15 @@ require ( github.com/google/go-cmp v0.7.0 github.com/gophercloud/gophercloud/v2 v2.12.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.29.0 - github.com/onsi/gomega v1.41.0 + github.com/onsi/ginkgo/v2 v2.31.0 + github.com/onsi/gomega v1.42.0 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 - golang.org/x/text v0.37.0 - k8s.io/api v0.34.8 - k8s.io/apimachinery v0.34.8 - k8s.io/client-go v0.34.8 - k8s.io/code-generator v0.34.8 + golang.org/x/text v0.38.0 + k8s.io/api v0.34.9 + k8s.io/apimachinery v0.34.9 + k8s.io/client-go v0.34.9 + k8s.io/code-generator v0.34.9 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 @@ -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.35.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/term v0.43.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.45.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-20251202230838-ff82c1b0f217 // indirect diff --git a/go.sum b/go.sum index 538dfc97f..fdd0cf7bb 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.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= -github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= -github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.31.0 h1:GtuJos5DFUV9EerYJo8RhYxosYNGvOdDE5haKq6Grfs= +github.com/onsi/ginkgo/v2 v2.31.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.42.0 h1:CJby8u36xb7v34W78F8WKvqTQP7PCMIPB78IVDB73l4= +github.com/onsi/gomega v1.42.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= 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,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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= 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= -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/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= 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.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= 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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= 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= @@ -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.8 h1:PATray7Ixf5fzuvW6WTrLGqWpaeSUlV3NqygBu10YC0= -k8s.io/api v0.34.8/go.mod h1:kkv5KLONEBxemk51GDsqR0TaBig60bVriYyDj9v3gg8= +k8s.io/api v0.34.9 h1:aVsK5NQL7146suJriGuvpi9giNpwIRSHJ8v5HWakwBo= +k8s.io/api v0.34.9/go.mod h1:8oYqD5tLKgvBSnkuDbHZTNrk7NTHybkfYjJ6lNjThjQ= 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.8 h1:Vjyd/TdakksL0g7ikxyaAk3s3qgqRRYz9vj0AudwNzc= -k8s.io/apimachinery v0.34.8/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= +k8s.io/apimachinery v0.34.9 h1:WuRPolTfoEST1TQe9sLcT1QAWLMT0CNE4Eqj29XuUmQ= +k8s.io/apimachinery v0.34.9/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= 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.8 h1:/4Ar6TX6V/vwhFQDeTMySO0+OBbslk7inkEdOQdR66A= -k8s.io/client-go v0.34.8/go.mod h1:VVLUD4GP16X+ePPZ3YpCyxn1rhaf1lczr/HCI0e40h8= -k8s.io/code-generator v0.34.8 h1:J2+jOASoj+Y0cDdypMAIiD3/bKqpfteRx8rLsYdnMCk= -k8s.io/code-generator v0.34.8/go.mod h1:q+hSPDPvkqknwUcUX5TtuGqI6GoRFSmN4PWLcHql/i0= +k8s.io/client-go v0.34.9 h1:HlhSEGPyCFH5rQADW6NPKEziGns6ekgUCPK0OsOGU90= +k8s.io/client-go v0.34.9/go.mod h1:bI3Sqqmwls2JKFZOQN9h8oLaeibvIA5pqqJAaSJWnrk= +k8s.io/code-generator v0.34.9 h1:jXBgPd5FbFoN1J2uA/qISW3uLHRl8wJ9Gx6L4PiJIPk= +k8s.io/code-generator v0.34.9/go.mod h1:/doiEA33AIV4kFt4NTMCzV8N5Ued8kq1fZMWUUib990= 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 0b914d085e115c0b1f1c3d1377eb238b71b5f1dc Mon Sep 17 00:00:00 2001 From: Roman Hros Date: Thu, 11 Jun 2026 12:05:58 +0200 Subject: [PATCH 204/237] Add flavor extra_specs Signed-off-by: Roman Hros --- api/v1alpha1/flavor_types.go | 48 ++++- api/v1alpha1/zz_generated.deepcopy.go | 40 ++++ cmd/models-schema/zz_generated.openapi.go | 102 ++++++++++ .../bases/openstack.k-orc.cloud_flavors.yaml | 70 ++++++- config/samples/openstack_v1alpha1_flavor.yaml | 5 + .../openstack_v1alpha1_volumetype.yaml | 6 +- internal/controllers/flavor/actuator.go | 113 ++++++++++- internal/controllers/flavor/actuator_test.go | 191 ++++++++++++++++++ internal/controllers/flavor/status.go | 5 + .../tests/flavor-create-full/00-assert.yaml | 5 +- .../00-create-resource.yaml | 3 + .../flavor/tests/flavor-update/00-assert.yaml | 29 +++ .../flavor-update/00-minimal-resource.yaml | 14 ++ .../tests/flavor-update/00-prerequisites.yaml | 5 + .../flavor/tests/flavor-update/01-assert.yaml | 22 ++ .../flavor-update/01-updated-resource.yaml | 14 ++ .../flavor/tests/flavor-update/02-assert.yaml | 29 +++ .../flavor-update/02-reverted-resource.yaml | 7 + .../flavor/tests/flavor-update/README.md | 17 ++ internal/osclients/compute.go | 16 ++ internal/osclients/mock/compute.go | 29 +++ .../api/v1alpha1/flavorextraspec.go | 48 +++++ .../api/v1alpha1/flavorextraspecstatus.go | 48 +++++ .../api/v1alpha1/flavorresourcespec.go | 32 ++- .../api/v1alpha1/flavorresourcestatus.go | 30 ++- .../applyconfiguration/internal/internal.go | 34 ++++ pkg/clients/applyconfiguration/utils.go | 4 + test/apivalidations/flavor_test.go | 55 ++++- website/docs/crd-reference.md | 36 ++++ 29 files changed, 1026 insertions(+), 31 deletions(-) create mode 100644 internal/controllers/flavor/tests/flavor-update/00-assert.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/00-minimal-resource.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/00-prerequisites.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/01-assert.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/01-updated-resource.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/02-assert.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/02-reverted-resource.yaml create mode 100644 internal/controllers/flavor/tests/flavor-update/README.md create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspecstatus.go diff --git a/api/v1alpha1/flavor_types.go b/api/v1alpha1/flavor_types.go index 4ebbeccf9..c6d6479bb 100644 --- a/api/v1alpha1/flavor_types.go +++ b/api/v1alpha1/flavor_types.go @@ -17,10 +17,10 @@ limitations under the License. package v1alpha1 // FlavorResourceSpec contains the desired state of a flavor -// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="FlavorResourceSpec is immutable" type FlavorResourceSpec struct { // name will be the name of the created resource. If not specified, the // name of the ORC object will be used. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable" // +optional Name *OpenStackName `json:"name,omitempty"` @@ -29,22 +29,26 @@ type FlavorResourceSpec struct { // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=255 // +kubebuilder:validation:Pattern=^[a-zA-Z0-9._-]([a-zA-Z0-9. _-]*[a-zA-Z0-9._-])?$ + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="id is immutable" // +optional ID string `json:"id,omitempty"` //nolint:kubeapilinter // intentionally allow raw ID // description contains a free form description of the flavor. // +kubebuilder:validation:MinLength:=1 // +kubebuilder:validation:MaxLength:=65535 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="description is immutable" // +optional Description *string `json:"description,omitempty"` // ram is the memory of the flavor, measured in MB. // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ram is immutable" // +required RAM int32 `json:"ram,omitempty"` // vcpus is the number of vcpus for the flavor. // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="vcpus is immutable" // +required Vcpus int32 `json:"vcpus,omitempty"` @@ -57,16 +61,26 @@ type FlavorResourceSpec struct { // zero root disk via the // os_compute_api:servers:create:zero_disk_flavor policy rule. // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="disk is immutable" // +required Disk int32 `json:"disk"` // 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. // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="swap is immutable" // +optional Swap int32 `json:"swap,omitempty"` + // extraSpecs is a list of key-value pairs that define extra specifications for the flavor. + // +kubebuilder:validation:MaxItems:=128 + // +listType=map + // +listMapKey=name + // +optional + ExtraSpecs []FlavorExtraSpec `json:"extraSpecs,omitempty"` + // isPublic flags a flavor as being available to all projects or not. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="isPublic is immutable" // +optional IsPublic *bool `json:"isPublic,omitempty"` @@ -75,6 +89,7 @@ type FlavorResourceSpec struct { // be used as a scratch space for applications that are aware of its // limitations. Defaults to 0. // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ephemeral is immutable" // +optional Ephemeral int32 `json:"ephemeral,omitempty"` } @@ -131,6 +146,12 @@ type FlavorResourceStatus struct { // +optional Swap *int32 `json:"swap,omitempty"` + // extraSpecs is a map of key-value pairs that define extra specifications for the flavor. + // +kubebuilder:validation:MaxItems:=128 + // +listType=atomic + // +optional + ExtraSpecs []FlavorExtraSpecStatus `json:"extraSpecs"` + // isPublic flags a flavor as being available to all projects or not. // +optional IsPublic *bool `json:"isPublic,omitempty"` @@ -139,3 +160,28 @@ type FlavorResourceStatus struct { // +optional Ephemeral *int32 `json:"ephemeral,omitempty"` } + +type FlavorExtraSpec struct { + // name is the name of the extraspec + // +kubebuilder:validation:Pattern="^[a-zA-Z0-9-_:. ]+$" + // +kubebuilder:validation:MaxLength:=255 + // +required + Name string `json:"name"` + + // value is the value of the extraspec + // +kubebuilder:validation:MaxLength:=255 + // +required + Value string `json:"value"` +} + +type FlavorExtraSpecStatus struct { + // name is the name of the extraspec + // +kubebuilder:validation:MaxLength:=255 + // +optional + Name string `json:"name,omitempty"` + + // value is the value of the extraspec + // +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 ead3ef708..2f4fe34aa 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1309,6 +1309,36 @@ func (in *Flavor) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlavorExtraSpec) DeepCopyInto(out *FlavorExtraSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorExtraSpec. +func (in *FlavorExtraSpec) DeepCopy() *FlavorExtraSpec { + if in == nil { + return nil + } + out := new(FlavorExtraSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlavorExtraSpecStatus) DeepCopyInto(out *FlavorExtraSpecStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorExtraSpecStatus. +func (in *FlavorExtraSpecStatus) DeepCopy() *FlavorExtraSpecStatus { + if in == nil { + return nil + } + out := new(FlavorExtraSpecStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FlavorFilter) DeepCopyInto(out *FlavorFilter) { *out = *in @@ -1414,6 +1444,11 @@ func (in *FlavorResourceSpec) DeepCopyInto(out *FlavorResourceSpec) { *out = new(string) **out = **in } + if in.ExtraSpecs != nil { + in, out := &in.ExtraSpecs, &out.ExtraSpecs + *out = make([]FlavorExtraSpec, len(*in)) + copy(*out, *in) + } if in.IsPublic != nil { in, out := &in.IsPublic, &out.IsPublic *out = new(bool) @@ -1454,6 +1489,11 @@ func (in *FlavorResourceStatus) DeepCopyInto(out *FlavorResourceStatus) { *out = new(int32) **out = **in } + if in.ExtraSpecs != nil { + in, out := &in.ExtraSpecs, &out.ExtraSpecs + *out = make([]FlavorExtraSpecStatus, len(*in)) + copy(*out, *in) + } if in.IsPublic != nil { in, out := &in.IsPublic, &out.IsPublic *out = new(bool) diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 5601a14eb..bbdaee5f0 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -78,6 +78,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FilterByServerTags": schema_openstack_resource_controller_v2_api_v1alpha1_FilterByServerTags(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FixedIPStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Flavor": schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorExtraSpec": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorExtraSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorExtraSpecStatus": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorExtraSpecStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorFilter": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorImport(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorList": schema_openstack_resource_controller_v2_api_v1alpha1_FlavorList(ref), @@ -2722,6 +2724,61 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Flavor(ref common.Refe } } +func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorExtraSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the extraspec", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the value of the extraspec", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorExtraSpecStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the extraspec", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "value is the value of the extraspec", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2899,6 +2956,28 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref Format: "int32", }, }, + "extraSpecs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "extraSpecs is a list of key-value pairs that define extra specifications for the flavor.", + 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.FlavorExtraSpec"), + }, + }, + }, + }, + }, "isPublic": { SchemaProps: spec.SchemaProps{ Description: "isPublic flags a flavor as being available to all projects or not.", @@ -2917,6 +2996,8 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceSpec(ref Required: []string{"ram", "vcpus", "disk"}, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorExtraSpec"}, } } @@ -2969,6 +3050,25 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceStatus(r Format: "int32", }, }, + "extraSpecs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "extraSpecs is a map of key-value pairs that define extra specifications for the flavor.", + 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.FlavorExtraSpecStatus"), + }, + }, + }, + }, + }, "isPublic": { SchemaProps: spec.SchemaProps{ Description: "isPublic flags a flavor as being available to all projects or not.", @@ -2986,6 +3086,8 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorResourceStatus(r }, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorExtraSpecStatus"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml index d85dd72f1..a86ac5630 100644 --- a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml @@ -167,6 +167,9 @@ spec: maxLength: 65535 minLength: 1 type: string + x-kubernetes-validations: + - message: description is immutable + rule: self == oldSelf disk: description: |- disk is the size of the root disk that will be created in GiB. If 0 @@ -180,6 +183,9 @@ spec: format: int32 minimum: 0 type: integer + x-kubernetes-validations: + - message: disk is immutable + rule: self == oldSelf ephemeral: description: |- ephemeral is the size of the ephemeral disk that will be created, in GiB. @@ -189,6 +195,32 @@ spec: format: int32 minimum: 0 type: integer + x-kubernetes-validations: + - message: ephemeral is immutable + rule: self == oldSelf + extraSpecs: + description: extraSpecs is a list of key-value pairs that define + extra specifications for the flavor. + items: + properties: + name: + description: name is the name of the extraspec + maxLength: 255 + pattern: ^[a-zA-Z0-9-_:. ]+$ + type: string + value: + description: value is the value of the extraspec + maxLength: 255 + type: string + required: + - name + - value + type: object + maxItems: 128 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map id: description: |- id will be the id of the created resource. If not specified, a random @@ -197,10 +229,16 @@ spec: minLength: 1 pattern: ^[a-zA-Z0-9._-]([a-zA-Z0-9. _-]*[a-zA-Z0-9._-])?$ type: string + x-kubernetes-validations: + - message: id is immutable + rule: self == oldSelf isPublic: description: isPublic flags a flavor as being available to all projects or not. type: boolean + x-kubernetes-validations: + - message: isPublic is immutable + rule: self == oldSelf name: description: |- name will be the name of the created resource. If not specified, the @@ -209,11 +247,17 @@ spec: minLength: 1 pattern: ^[^,]+$ type: string + x-kubernetes-validations: + - message: name is immutable + rule: self == oldSelf ram: description: ram is the memory of the flavor, measured in MB. format: int32 minimum: 1 type: integer + x-kubernetes-validations: + - message: ram is immutable + rule: self == oldSelf swap: description: |- swap is the size of a dedicated swap disk that will be allocated, in @@ -221,19 +265,22 @@ spec: format: int32 minimum: 0 type: integer + x-kubernetes-validations: + - message: swap is immutable + rule: self == oldSelf vcpus: description: vcpus is the number of vcpus for the flavor. format: int32 minimum: 1 type: integer + x-kubernetes-validations: + - message: vcpus is immutable + rule: self == oldSelf required: - disk - ram - vcpus type: object - x-kubernetes-validations: - - message: FlavorResourceSpec is immutable - rule: self == oldSelf required: - cloudCredentialsRef type: object @@ -351,6 +398,23 @@ spec: description: ephemeral is the size of the ephemeral disk, in GiB. format: int32 type: integer + extraSpecs: + description: extraSpecs is a map of key-value pairs that define + extra specifications for the flavor. + items: + properties: + name: + description: name is the name of the extraspec + maxLength: 255 + type: string + value: + description: value is the value of the extraspec + maxLength: 255 + type: string + type: object + maxItems: 128 + type: array + x-kubernetes-list-type: atomic isPublic: description: isPublic flags a flavor as being available to all projects or not. diff --git a/config/samples/openstack_v1alpha1_flavor.yaml b/config/samples/openstack_v1alpha1_flavor.yaml index 87990fb6c..c46f5bc5e 100644 --- a/config/samples/openstack_v1alpha1_flavor.yaml +++ b/config/samples/openstack_v1alpha1_flavor.yaml @@ -15,3 +15,8 @@ spec: swap: 2 isPublic: false ephemeral: 1 + extraSpecs: + - name: spec1 + value: foo + - name: spec2 + value: bar diff --git a/config/samples/openstack_v1alpha1_volumetype.yaml b/config/samples/openstack_v1alpha1_volumetype.yaml index 1e8ee928e..77c8d458d 100644 --- a/config/samples/openstack_v1alpha1_volumetype.yaml +++ b/config/samples/openstack_v1alpha1_volumetype.yaml @@ -12,5 +12,7 @@ spec: description: Sample VolumeType isPublic: false extraSpecs: - spec1: "foo" - spec2: "bar" + - name: spec1 + value: foo + - name: spec2 + value: bar diff --git a/internal/controllers/flavor/actuator.go b/internal/controllers/flavor/actuator.go index f80146268..d34cf09fc 100644 --- a/internal/controllers/flavor/actuator.go +++ b/internal/controllers/flavor/actuator.go @@ -28,6 +28,7 @@ import ( orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" generic "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" osclients "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" ) @@ -36,16 +37,20 @@ import ( type ( osResourceT = flavors.Flavor - createResourceActuator = generic.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] - deleteResourceActuator = generic.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] - helperFactory = generic.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] + createResourceActuator = generic.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = generic.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + reconcileResourceActuator = generic.ReconcileResourceActuator[orcObjectPT, osResourceT] + resourceReconciler = generic.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = generic.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] ) type flavorClient interface { GetFlavor(context.Context, string) (*flavors.Flavor, error) ListFlavors(context.Context, flavors.ListOptsBuilder) iter.Seq2[*flavors.Flavor, error] CreateFlavor(context.Context, flavors.CreateOptsBuilder) (*flavors.Flavor, error) + CreateFlavorExtraSpecs(context.Context, string, flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) DeleteFlavor(context.Context, string) error + DeleteFlavorExtraSpec(context.Context, string, string) error } type flavorActuator struct { @@ -171,6 +176,108 @@ func (actuator flavorActuator) DeleteResource(ctx context.Context, _ orcObjectPT return progress.WrapError(actuator.osClient.DeleteFlavor(ctx, flavor.ID)) } +func (actuator flavorActuator) reconcileExtraSpecs(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + return nil + } + + desiredExtraSpecs := extraSpecsToMap(resource.ExtraSpecs) + currentExtraSpecs := osResource.ExtraSpecs + + updates := extraSpecUpdates(desiredExtraSpecs, currentExtraSpecs) + deletes := extraSpecDeletes(desiredExtraSpecs, currentExtraSpecs) + + if len(updates) == 0 && len(deletes) == 0 { + log.V(logging.Debug).Info("No changes") + return nil + } + + if len(updates) > 0 { + _, err := actuator.osClient.CreateFlavorExtraSpecs( + ctx, + osResource.ID, + flavors.ExtraSpecsOpts(updates), + ) + if err != nil { + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal( + orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration updating resource extra specs: "+err.Error(), + err, + ) + } + return progress.WrapError(err) + } + } + + for _, d := range deletes { + if err := actuator.osClient.DeleteFlavorExtraSpec( + ctx, + osResource.ID, + d, + ); err != nil { + if orcerrors.IsNotFound(err) { + continue + } + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal( + orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid configuration deleting resource extra spec: "+err.Error(), + err, + ) + } + return progress.WrapError(err) + } + } + + return progress.NeedsRefresh() +} + +func extraSpecsToMap(extraSpecs []orcv1alpha1.FlavorExtraSpec) map[string]string { + specs := make(map[string]string) + + for _, spec := range extraSpecs { + specs[spec.Name] = spec.Value + } + + return specs +} + +func extraSpecUpdates(desired, current map[string]string) map[string]string { + updates := make(map[string]string) + + for k, v := range desired { + cur, exists := current[k] + if !exists || cur != v { + updates[k] = v + } + } + + return updates +} + +func extraSpecDeletes(desired, current map[string]string) []string { + var deletes []string + + for k := range current { + if _, found := desired[k]; !found { + deletes = append(deletes, k) + } + } + + return deletes +} + +var _ reconcileResourceActuator = flavorActuator{} + +func (actuator flavorActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller generic.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.reconcileExtraSpecs, + }, nil +} + type flavorHelperFactory struct{} var _ helperFactory = flavorHelperFactory{} diff --git a/internal/controllers/flavor/actuator_test.go b/internal/controllers/flavor/actuator_test.go index 40be7dd4e..6f7b171cc 100644 --- a/internal/controllers/flavor/actuator_test.go +++ b/internal/controllers/flavor/actuator_test.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "iter" + "reflect" + "sort" "testing" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/flavors" @@ -44,10 +46,18 @@ func (l mockFlavorClient) CreateFlavor(_ context.Context, _ flavors.CreateOptsBu return nil, errNotImplemented } +func (l mockFlavorClient) CreateFlavorExtraSpecs(_ context.Context, _ string, _ flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) { + return nil, errNotImplemented +} + func (l mockFlavorClient) DeleteFlavor(_ context.Context, _ string) error { return errNotImplemented } +func (l mockFlavorClient) DeleteFlavorExtraSpec(_ context.Context, _, _ string) error { + return errNotImplemented +} + type flavorResult struct { flavor *flavors.Flavor err error @@ -377,3 +387,184 @@ func TestGetFlavorBySpec(t *testing.T) { }) } } + +func TestExtraSpecUpdates(t *testing.T) { + tests := []struct { + name string + desired map[string]string + current map[string]string + expected map[string]string + }{ + { + name: "No changes", + desired: map[string]string{"a": "1"}, + current: map[string]string{"a": "1"}, + expected: map[string]string{}, + }, + { + name: "Create new key", + desired: map[string]string{"a": "1"}, + current: map[string]string{}, + expected: map[string]string{"a": "1"}, + }, + { + name: "Update value", + desired: map[string]string{"a": "2"}, + current: map[string]string{"a": "1"}, + expected: map[string]string{"a": "2"}, + }, + { + name: "Multiple keys mixed", + desired: map[string]string{"a": "2", "b": "1"}, + current: map[string]string{"a": "1", "c": "9"}, + expected: map[string]string{"a": "2", "b": "1"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := extraSpecUpdates(tt.desired, tt.current) + + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("extraSpecUpdates() = %#v, want %#v", got, tt.expected) + } + }) + } +} + +func TestExtraSpecDeletes(t *testing.T) { + tests := []struct { + name string + desired map[string]string + current map[string]string + expected []string + }{ + { + name: "No deletes", + desired: map[string]string{"a": "1"}, + current: map[string]string{"a": "1"}, + expected: nil, + }, + { + name: "Delete missing key", + desired: map[string]string{}, + current: map[string]string{"a": "1"}, + expected: []string{"a"}, + }, + { + name: "Partial delete", + desired: map[string]string{"a": "1"}, + current: map[string]string{"a": "1", "b": "2"}, + expected: []string{"b"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := extraSpecDeletes(tt.desired, tt.current) + + sort.Strings(got) + sort.Strings(tt.expected) + + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("extraSpecDeletes() = %#v, want %#v", got, tt.expected) + } + }) + } +} + +type updateMockFlavorClient struct { + mockFlavorClient + errCreate error + errDelete error +} + +func (m updateMockFlavorClient) CreateFlavorExtraSpecs(_ context.Context, _ string, _ flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) { + return nil, m.errCreate +} + +func (m updateMockFlavorClient) DeleteFlavorExtraSpec(_ context.Context, _, _ string) error { + return m.errDelete +} + +func TestReconcileExtraSpecs(t *testing.T) { + tests := []struct { + name string + specSpecs []orcv1alpha1.FlavorExtraSpec + currSpecs map[string]string + client updateMockFlavorClient + expectError error + expectRefresh bool + }{ + { + name: "No changes needed", + specSpecs: []orcv1alpha1.FlavorExtraSpec{{Name: "hw:numa_nodes", Value: "2"}}, + currSpecs: map[string]string{"hw:numa_nodes": "2"}, + client: updateMockFlavorClient{}, + expectError: nil, + expectRefresh: false, + }, + { + name: "Successful modification", + specSpecs: []orcv1alpha1.FlavorExtraSpec{{Name: "hw:numa_nodes", Value: "4"}}, + currSpecs: map[string]string{"hw:numa_nodes": "2"}, + client: updateMockFlavorClient{}, + expectError: nil, + expectRefresh: true, + }, + { + name: "Update fails early", + specSpecs: []orcv1alpha1.FlavorExtraSpec{{Name: "new_key", Value: "true"}}, + currSpecs: map[string]string{}, + client: updateMockFlavorClient{errCreate: errTest}, + expectError: errTest, + expectRefresh: false, + }, + { + name: "Update succeeds but delete fails", + specSpecs: []orcv1alpha1.FlavorExtraSpec{}, + currSpecs: map[string]string{"old_key": "remove-me"}, + client: updateMockFlavorClient{errDelete: errTest}, + expectError: errTest, + expectRefresh: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + actuator := flavorActuator{tt.client} + + obj := &orcv1alpha1.Flavor{ + Spec: orcv1alpha1.FlavorSpec{ + Resource: &orcv1alpha1.FlavorResourceSpec{ + ExtraSpecs: tt.specSpecs, + }, + }, + } + + osResource := &flavors.Flavor{ + ID: "test-flavor-id", + ExtraSpecs: tt.currSpecs, + } + + status := actuator.reconcileExtraSpecs(ctx, obj, osResource) + + if tt.expectError != nil { + if status == nil || status.GetError() == nil { + t.Fatalf("Expected error %v, got none", tt.expectError) + } + if !errors.Is(status.GetError(), tt.expectError) { + t.Errorf("Expected error %v, got %v", tt.expectError, status.GetError()) + } + } else { + if status != nil && status.GetError() != nil { + t.Errorf("Unexpected error: %v", status.GetError()) + } + gotRefresh := (status != nil) + if gotRefresh != tt.expectRefresh { + t.Errorf("Refresh expectation mismatch: expected %v, got %v", tt.expectRefresh, gotRefresh) + } + } + }) + } +} diff --git a/internal/controllers/flavor/status.go b/internal/controllers/flavor/status.go index 697df64ef..3c2b4d76a 100644 --- a/internal/controllers/flavor/status.go +++ b/internal/controllers/flavor/status.go @@ -65,6 +65,11 @@ func (flavorStatusWriter) ApplyResourceStatus(_ logr.Logger, osResource *flavors if osResource.Ephemeral > 0 { resourceStatus.WithEphemeral(int32(osResource.Ephemeral)) } + for k, v := range osResource.ExtraSpecs { + resourceStatus.WithExtraSpecs(orcapplyconfigv1alpha1.FlavorExtraSpecStatus(). + WithName(k). + WithValue(v)) + } if osResource.Description != "" { resourceStatus.WithDescription(osResource.Description) } diff --git a/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml b/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml index dd94b805e..94a024ae5 100644 --- a/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml +++ b/internal/controllers/flavor/tests/flavor-create-full/00-assert.yaml @@ -13,4 +13,7 @@ status: disk: 20 swap: 2 isPublic: false - ephemeral: 1 \ No newline at end of file + ephemeral: 1 + extraSpecs: + - name: spec + value: specValue diff --git a/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml b/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml index 470180d72..de6789f29 100644 --- a/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml +++ b/internal/controllers/flavor/tests/flavor-create-full/00-create-resource.yaml @@ -18,3 +18,6 @@ spec: isPublic: false ephemeral: 1 id: testId-123 + extraSpecs: + - name: spec + value: specValue diff --git a/internal/controllers/flavor/tests/flavor-update/00-assert.yaml b/internal/controllers/flavor/tests/flavor-update/00-assert.yaml new file mode 100644 index 000000000..ffe085e81 --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/00-assert.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Flavor + name: flavor-update + ref: flavor +assertAll: + - celExpr: "!has(flavor.status.resource.extraSpecs)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: flavor-update +status: + resource: + disk: 0 + isPublic: true + name: flavor-update + ram: 1 + vcpus: 2 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/flavor/tests/flavor-update/00-minimal-resource.yaml b/internal/controllers/flavor/tests/flavor-update/00-minimal-resource.yaml new file mode 100644 index 000000000..827048769 --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/00-minimal-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: flavor-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + ram: 1 + vcpus: 2 + disk: 0 diff --git a/internal/controllers/flavor/tests/flavor-update/00-prerequisites.yaml b/internal/controllers/flavor/tests/flavor-update/00-prerequisites.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/00-prerequisites.yaml @@ -0,0 +1,5 @@ +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/flavor/tests/flavor-update/01-assert.yaml b/internal/controllers/flavor/tests/flavor-update/01-assert.yaml new file mode 100644 index 000000000..b98226b93 --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/01-assert.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: flavor-update +status: + resource: + disk: 0 + extraSpecs: + - name: spec + value: specValue + isPublic: true + name: flavor-update + ram: 1 + vcpus: 2 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/flavor/tests/flavor-update/01-updated-resource.yaml b/internal/controllers/flavor/tests/flavor-update/01-updated-resource.yaml new file mode 100644 index 000000000..9276df64b --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/01-updated-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: flavor-update +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + extraSpecs: + - name: spec + value: specValue diff --git a/internal/controllers/flavor/tests/flavor-update/02-assert.yaml b/internal/controllers/flavor/tests/flavor-update/02-assert.yaml new file mode 100644 index 000000000..ffe085e81 --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/02-assert.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Flavor + name: flavor-update + ref: flavor +assertAll: + - celExpr: "!has(flavor.status.resource.extraSpecs)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Flavor +metadata: + name: flavor-update +status: + resource: + disk: 0 + isPublic: true + name: flavor-update + ram: 1 + vcpus: 2 + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/flavor/tests/flavor-update/02-reverted-resource.yaml b/internal/controllers/flavor/tests/flavor-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-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/flavor/tests/flavor-update/README.md b/internal/controllers/flavor/tests/flavor-update/README.md new file mode 100644 index 000000000..93708de17 --- /dev/null +++ b/internal/controllers/flavor/tests/flavor-update/README.md @@ -0,0 +1,17 @@ +# Update Flavor + +## Step 00 + +Create a Flavor using only mandatory fields. + +## Step 01 + +Update all mutable fields. + +## Step 02 + +Revert the resource to its original value and verify the resulting object is similar to when if was first created. + +## Reference + +https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/osclients/compute.go b/internal/osclients/compute.go index 43f4396d5..59bec1621 100644 --- a/internal/osclients/compute.go +++ b/internal/osclients/compute.go @@ -49,8 +49,10 @@ const NovaMinimumMicroversion = "2.71" type ComputeClient interface { CreateFlavor(ctx context.Context, opts flavors.CreateOptsBuilder) (*flavors.Flavor, error) + CreateFlavorExtraSpecs(ctx context.Context, id string, opts flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) GetFlavor(ctx context.Context, id string) (*flavors.Flavor, error) DeleteFlavor(ctx context.Context, id string) error + DeleteFlavorExtraSpec(ctx context.Context, id, key string) error ListFlavors(ctx context.Context, listOpts flavors.ListOptsBuilder) iter.Seq2[*flavors.Flavor, error] CreateServer(ctx context.Context, createOpts servers.CreateOptsBuilder, schedulerHints servers.SchedulerHintOptsBuilder) (*servers.Server, error) @@ -107,10 +109,18 @@ func (c computeClient) CreateFlavor(ctx context.Context, opts flavors.CreateOpts return flavors.Create(ctx, c.client, opts).Extract() } +func (c computeClient) CreateFlavorExtraSpecs(ctx context.Context, id string, opts flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) { + return flavors.CreateExtraSpecs(ctx, c.client, id, opts).Extract() +} + func (c computeClient) DeleteFlavor(ctx context.Context, id string) error { return flavors.Delete(ctx, c.client, id).ExtractErr() } +func (c computeClient) DeleteFlavorExtraSpec(ctx context.Context, id, key string) error { + return flavors.DeleteExtraSpec(ctx, c.client, id, key).ExtractErr() +} + func (c computeClient) ListFlavors(ctx context.Context, opts flavors.ListOptsBuilder) iter.Seq2[*flavors.Flavor, error] { pager := flavors.ListDetail(c.client, opts) return func(yield func(*flavors.Flavor, error) bool) { @@ -201,12 +211,18 @@ func NewComputeErrorClient(e error) ComputeClient { func (e computeErrorClient) CreateFlavor(ctx context.Context, opts flavors.CreateOptsBuilder) (*flavors.Flavor, error) { return nil, e.error } +func (e computeErrorClient) CreateFlavorExtraSpecs(ctx context.Context, id string, opts flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) { + return nil, e.error +} func (e computeErrorClient) GetFlavor(ctx context.Context, id string) (*flavors.Flavor, error) { return nil, e.error } func (e computeErrorClient) DeleteFlavor(ctx context.Context, id string) error { return e.error } +func (e computeErrorClient) DeleteFlavorExtraSpec(ctx context.Context, id, key string) error { + return e.error +} func (e computeErrorClient) ListFlavors(_ context.Context, _ flavors.ListOptsBuilder) iter.Seq2[*flavors.Flavor, error] { return func(yield func(*flavors.Flavor, error) bool) { yield(nil, e.error) diff --git a/internal/osclients/mock/compute.go b/internal/osclients/mock/compute.go index 2fdea0de4..1d73fbf72 100644 --- a/internal/osclients/mock/compute.go +++ b/internal/osclients/mock/compute.go @@ -92,6 +92,21 @@ func (mr *MockComputeClientMockRecorder) CreateFlavor(ctx, opts any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFlavor", reflect.TypeOf((*MockComputeClient)(nil).CreateFlavor), ctx, opts) } +// CreateFlavorExtraSpecs mocks base method. +func (m *MockComputeClient) CreateFlavorExtraSpecs(ctx context.Context, id string, opts flavors.CreateExtraSpecsOptsBuilder) (map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateFlavorExtraSpecs", ctx, id, opts) + ret0, _ := ret[0].(map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateFlavorExtraSpecs indicates an expected call of CreateFlavorExtraSpecs. +func (mr *MockComputeClientMockRecorder) CreateFlavorExtraSpecs(ctx, id, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFlavorExtraSpecs", reflect.TypeOf((*MockComputeClient)(nil).CreateFlavorExtraSpecs), ctx, id, opts) +} + // CreateServer mocks base method. func (m *MockComputeClient) CreateServer(ctx context.Context, createOpts servers.CreateOptsBuilder, schedulerHints servers.SchedulerHintOptsBuilder) (*servers.Server, error) { m.ctrl.T.Helper() @@ -165,6 +180,20 @@ func (mr *MockComputeClientMockRecorder) DeleteFlavor(ctx, id any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFlavor", reflect.TypeOf((*MockComputeClient)(nil).DeleteFlavor), ctx, id) } +// DeleteFlavorExtraSpec mocks base method. +func (m *MockComputeClient) DeleteFlavorExtraSpec(ctx context.Context, id, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteFlavorExtraSpec", ctx, id, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteFlavorExtraSpec indicates an expected call of DeleteFlavorExtraSpec. +func (mr *MockComputeClientMockRecorder) DeleteFlavorExtraSpec(ctx, id, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFlavorExtraSpec", reflect.TypeOf((*MockComputeClient)(nil).DeleteFlavorExtraSpec), ctx, id, key) +} + // DeleteServer mocks base method. func (m *MockComputeClient) DeleteServer(ctx context.Context, serverID string) error { m.ctrl.T.Helper() diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspec.go new file mode 100644 index 000000000..042cc0455 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspec.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 + +// FlavorExtraSpecApplyConfiguration represents a declarative configuration of the FlavorExtraSpec type for use +// with apply. +type FlavorExtraSpecApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Value *string `json:"value,omitempty"` +} + +// FlavorExtraSpecApplyConfiguration constructs a declarative configuration of the FlavorExtraSpec type for use with +// apply. +func FlavorExtraSpec() *FlavorExtraSpecApplyConfiguration { + return &FlavorExtraSpecApplyConfiguration{} +} + +// 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 *FlavorExtraSpecApplyConfiguration) WithName(value string) *FlavorExtraSpecApplyConfiguration { + b.Name = &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 *FlavorExtraSpecApplyConfiguration) WithValue(value string) *FlavorExtraSpecApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspecstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspecstatus.go new file mode 100644 index 000000000..6fabec4a5 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorextraspecstatus.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 + +// FlavorExtraSpecStatusApplyConfiguration represents a declarative configuration of the FlavorExtraSpecStatus type for use +// with apply. +type FlavorExtraSpecStatusApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Value *string `json:"value,omitempty"` +} + +// FlavorExtraSpecStatusApplyConfiguration constructs a declarative configuration of the FlavorExtraSpecStatus type for use with +// apply. +func FlavorExtraSpecStatus() *FlavorExtraSpecStatusApplyConfiguration { + return &FlavorExtraSpecStatusApplyConfiguration{} +} + +// 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 *FlavorExtraSpecStatusApplyConfiguration) WithName(value string) *FlavorExtraSpecStatusApplyConfiguration { + b.Name = &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 *FlavorExtraSpecStatusApplyConfiguration) WithValue(value string) *FlavorExtraSpecStatusApplyConfiguration { + b.Value = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go index 4fc4161e0..28721f084 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcespec.go @@ -25,15 +25,16 @@ import ( // FlavorResourceSpecApplyConfiguration represents a declarative configuration of the FlavorResourceSpec type for use // with apply. type FlavorResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - ID *string `json:"id,omitempty"` - Description *string `json:"description,omitempty"` - RAM *int32 `json:"ram,omitempty"` - Vcpus *int32 `json:"vcpus,omitempty"` - Disk *int32 `json:"disk,omitempty"` - Swap *int32 `json:"swap,omitempty"` - IsPublic *bool `json:"isPublic,omitempty"` - Ephemeral *int32 `json:"ephemeral,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + RAM *int32 `json:"ram,omitempty"` + Vcpus *int32 `json:"vcpus,omitempty"` + Disk *int32 `json:"disk,omitempty"` + Swap *int32 `json:"swap,omitempty"` + ExtraSpecs []FlavorExtraSpecApplyConfiguration `json:"extraSpecs,omitempty"` + IsPublic *bool `json:"isPublic,omitempty"` + Ephemeral *int32 `json:"ephemeral,omitempty"` } // FlavorResourceSpecApplyConfiguration constructs a declarative configuration of the FlavorResourceSpec type for use with @@ -98,6 +99,19 @@ func (b *FlavorResourceSpecApplyConfiguration) WithSwap(value int32) *FlavorReso return b } +// WithExtraSpecs adds the given value to the ExtraSpecs 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 ExtraSpecs field. +func (b *FlavorResourceSpecApplyConfiguration) WithExtraSpecs(values ...*FlavorExtraSpecApplyConfiguration) *FlavorResourceSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithExtraSpecs") + } + b.ExtraSpecs = append(b.ExtraSpecs, *values[i]) + } + return b +} + // WithIsPublic sets the IsPublic field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the IsPublic field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go index 7b4996ed4..ac47f6311 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorresourcestatus.go @@ -21,14 +21,15 @@ package v1alpha1 // FlavorResourceStatusApplyConfiguration represents a declarative configuration of the FlavorResourceStatus type for use // with apply. type FlavorResourceStatusApplyConfiguration struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - RAM *int32 `json:"ram,omitempty"` - Vcpus *int32 `json:"vcpus,omitempty"` - Disk *int32 `json:"disk,omitempty"` - Swap *int32 `json:"swap,omitempty"` - IsPublic *bool `json:"isPublic,omitempty"` - Ephemeral *int32 `json:"ephemeral,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + RAM *int32 `json:"ram,omitempty"` + Vcpus *int32 `json:"vcpus,omitempty"` + Disk *int32 `json:"disk,omitempty"` + Swap *int32 `json:"swap,omitempty"` + ExtraSpecs []FlavorExtraSpecStatusApplyConfiguration `json:"extraSpecs,omitempty"` + IsPublic *bool `json:"isPublic,omitempty"` + Ephemeral *int32 `json:"ephemeral,omitempty"` } // FlavorResourceStatusApplyConfiguration constructs a declarative configuration of the FlavorResourceStatus type for use with @@ -85,6 +86,19 @@ func (b *FlavorResourceStatusApplyConfiguration) WithSwap(value int32) *FlavorRe return b } +// WithExtraSpecs adds the given value to the ExtraSpecs 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 ExtraSpecs field. +func (b *FlavorResourceStatusApplyConfiguration) WithExtraSpecs(values ...*FlavorExtraSpecStatusApplyConfiguration) *FlavorResourceStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithExtraSpecs") + } + b.ExtraSpecs = append(b.ExtraSpecs, *values[i]) + } + return b +} + // WithIsPublic sets the IsPublic field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the IsPublic 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 10461ed9c..abfc36fe8 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -641,6 +641,26 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorStatus default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorExtraSpec + map: + fields: + - name: name + type: + scalar: string + default: "" + - name: value + type: + scalar: string + default: "" +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorExtraSpecStatus + map: + fields: + - name: name + type: + scalar: string + - name: value + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorFilter map: fields: @@ -678,6 +698,14 @@ var schemaYAML = typed.YAMLObject(`types: - name: ephemeral type: scalar: numeric + - name: extraSpecs + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorExtraSpec + elementRelationship: associative + keys: + - name - name: id type: scalar: string @@ -708,6 +736,12 @@ var schemaYAML = typed.YAMLObject(`types: - name: ephemeral type: scalar: numeric + - name: extraSpecs + type: + list: + elementType: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorExtraSpecStatus + elementRelationship: atomic - name: isPublic type: scalar: boolean diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 5d8f68cd9..1e5ffabc0 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -120,6 +120,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.FixedIPStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Flavor"): return &apiv1alpha1.FlavorApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("FlavorExtraSpec"): + return &apiv1alpha1.FlavorExtraSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("FlavorExtraSpecStatus"): + return &apiv1alpha1.FlavorExtraSpecStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("FlavorFilter"): return &apiv1alpha1.FlavorFilterApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("FlavorImport"): diff --git a/test/apivalidations/flavor_test.go b/test/apivalidations/flavor_test.go index 847af3a15..25f4659fe 100644 --- a/test/apivalidations/flavor_test.go +++ b/test/apivalidations/flavor_test.go @@ -98,13 +98,51 @@ var _ = Describe("ORC Flavor API validations", func() { }, }) - It("should be immutable", func(ctx context.Context) { + It("should be immutable except extraSpecs", func(ctx context.Context) { flavor := flavorStub(namespace) + patch := baseFlavorPatch(flavor) - patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithRAM(1).WithVcpus(1).WithDisk(1)) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec(). + WithName("base-name"). + WithID("base-id"). + WithDescription("base-desc"). + WithRAM(1). + WithVcpus(1). + WithDisk(1). + WithSwap(1). + WithIsPublic(true). + WithEphemeral(1). + WithExtraSpecs( + applyconfigv1alpha1.FlavorExtraSpec(). + WithName("spec"). + WithValue("specValue"), + ), + ) Expect(applyObj(ctx, flavor, patch)).To(Succeed()) - patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithRAM(2).WithVcpus(1).WithDisk(1)) - Expect(applyObj(ctx, flavor, patch)).To(MatchError(ContainSubstring("FlavorResourceSpec is immutable"))) + + patch = baseFlavorPatch(flavor) + patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec(). + WithName("mutated-name"). + WithID("mutated-id"). + WithDescription("mutated-desc"). + WithRAM(2). + WithVcpus(2). + WithDisk(2). + WithSwap(2). + WithIsPublic(false). + WithEphemeral(2). + WithExtraSpecs( + applyconfigv1alpha1.FlavorExtraSpec(). + WithName("spec2"). + WithValue("specValue2"), + ), + ) + err := applyObj(ctx, flavor, patch) + fields := []string{"name", "id", "description", "ram", "vcpus", "disk", "swap", "isPublic", "ephemeral"} + for _, field := range fields { + Expect(err).To(MatchError(ContainSubstring(field + " is immutable"))) + } + Expect(err.Error()).To(Not(ContainSubstring("extraSpecs is immutable"))) }) It("should reject a flavor without required fields", func(ctx context.Context) { @@ -164,4 +202,13 @@ var _ = Describe("ORC Flavor API validations", func() { patch.Spec.WithResource(applyconfigv1alpha1.FlavorResourceSpec().WithID("test.id -123_").WithRAM(1).WithVcpus(1).WithDescription("test").WithDisk(1)) Expect(applyObj(ctx, flavor, patch)).To(Succeed()) }) + + It("should permit extraSpecs with required fields", func(ctx context.Context) { + flavor := flavorStub(namespace) + patch := baseFlavorPatch(flavor) + patch.Spec.WithResource(testFlavorResource(). + WithExtraSpecs(applyconfigv1alpha1.FlavorExtraSpec(). + WithName("key").WithValue("value"))) + Expect(applyObj(ctx, flavor, patch)).To(Succeed()) + }) }) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index de769a2d0..1ccbb7352 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -965,6 +965,40 @@ Flavor is the Schema for an ORC resource. | `status` _[FlavorStatus](#flavorstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| +#### FlavorExtraSpec + + + + + + + +_Appears in:_ +- [FlavorResourceSpec](#flavorresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is the name of the extraspec | | MaxLength: 255
Pattern: `^[a-zA-Z0-9-_:. ]+$`
Required: \{\}
| +| `value` _string_ | value is the value of the extraspec | | MaxLength: 255
Required: \{\}
| + + +#### FlavorExtraSpecStatus + + + + + + + +_Appears in:_ +- [FlavorResourceStatus](#flavorresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _string_ | name is the name of the extraspec | | MaxLength: 255
Optional: \{\}
| +| `value` _string_ | value is the value of the extraspec | | MaxLength: 255
Optional: \{\}
| + + #### FlavorFilter @@ -1025,6 +1059,7 @@ _Appears in:_ | `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: \{\}
| +| `extraSpecs` _[FlavorExtraSpec](#flavorextraspec) array_ | extraSpecs is a list of key-value pairs that define extra specifications for the flavor. | | MaxItems: 128
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: \{\}
| @@ -1048,6 +1083,7 @@ _Appears in:_ | `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: \{\}
| +| `extraSpecs` _[FlavorExtraSpecStatus](#flavorextraspecstatus) array_ | extraSpecs is a map of key-value pairs that define extra specifications for the flavor. | | MaxItems: 128
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: \{\}
| From 90aa925acee121621945f7570455dba04c0cbfdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:15:12 +0000 Subject: [PATCH 205/237] :seedling:(deps): Bump the all-github-actions group with 2 updates Bumps the all-github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action). Updates `actions/checkout` from 6.0.3 to 7.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0) Updates `zizmorcore/zizmor-action` from 0.5.6 to 0.5.7 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/5f14fd08f7cf1cb1609c1e344975f152c7ee938d...192e21d79ab29983730a13d1382995c2307fbcaa) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.7 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/pr-dependabot.yaml | 2 +- .github/workflows/release_image.yaml | 2 +- .github/workflows/semver.yaml | 2 +- .github/workflows/unit.yml | 2 +- .github/workflows/website.yaml | 2 +- .github/workflows/weekly-security-scan.yaml | 2 +- .github/workflows/zizmor.yaml | 4 ++-- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index 50792d9dc..6e904b175 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 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 d19ccc71e..ae7b58fbd 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index b09d49f91..822fb5efe 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # tag=v1.3.0 diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index e568fb9e4..fa8a506f1 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index a4f352f6b..ecc415179 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index 7f6da21f0..a18068ff3 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 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/release_image.yaml b/.github/workflows/release_image.yaml index 5fd4e1574..9b69ef4ca 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: # Required for git describe to generate correct output for populating # build variables diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index b5cff83da..60e141a06 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -13,7 +13,7 @@ jobs: analyze: runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 68224b27c..0a511fb3c 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -17,7 +17,7 @@ jobs: - '1' steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index d2f94d363..a4ae0d37f 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index 2194bd78f..efabf9d56 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: ref: ${{ matrix.branch }} persist-credentials: false diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index 598005ade..fe1b4b3e9 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -20,9 +20,9 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # tag=v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 with: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # tag=v0.5.6 + uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # tag=v0.5.7 From 5bde31a70707466a55ec5e286ea3652ab6ce713f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 30 Jun 2026 09:23:01 +0200 Subject: [PATCH 206/237] AGENTS.md: Document missing patterns and fix inaccuracies --- AGENTS.md | 176 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 161 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7795e08f0..e91759dda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,11 +21,21 @@ openstack-resource-controller/ │ │ ├── status.go # Status writer implementation │ │ ├── zz_generated.*.go # Generated code (DO NOT EDIT) │ │ └── tests/ # KUTTL E2E tests +│ ├── logging/ # Log level constants │ ├── osclients/ # OpenStack API client wrappers │ ├── scope/ # Cloud credentials & client factory -│ └── util/ # Utilities (errors, dependency, tags) +│ └── util/ +│ ├── applyconfigs/ # SSA apply config helpers +│ ├── credentials/ # Credential watch & dependency setup +│ ├── dependency/ # Dependency framework +│ ├── errors/ # Error classification (Terminal, IsRetryable) +│ ├── finalizers/ # Finalizer helpers +│ ├── result/ # Result helpers +│ ├── strings/ # Finalizer/field-owner name generation +│ └── tags/ # Tag reconciliation utilities ├── cmd/ │ ├── manager/ # Main entry point +│ ├── models-schema/ # OpenAPI schema generation │ ├── resource-generator/ # Code generation │ └── scaffold-controller/ # New controller scaffolding └── website/docs/development/ # Detailed documentation @@ -44,7 +54,7 @@ All controllers use a generic reconciler that handles the reconciliation loop. C ### Key Interfaces -Controllers implement these methods (see `internal/controllers/flavor/` for a simple example): +Controllers implement these methods (see `internal/controllers/servergroup/` for a simple example): ```go // Required by all actuators @@ -60,7 +70,7 @@ CreateResource(ctx, orcObject) (*osResource, ReconcileStatus) DeleteResource(ctx, orcObject, osResource) ReconcileStatus // Optional - for updates after creation -GetResourceReconcilers(ctx, obj, osResource) ([]ResourceReconciler, error) +GetResourceReconcilers(ctx, obj, osResource, controller) ([]ResourceReconciler, ReconcileStatus) ``` ### Two Critical Conditions @@ -79,11 +89,17 @@ Every ORC object has these conditions: Methods return `ReconcileStatus` instead of `error`: +`ReconcileStatus` is a type alias for a pointer (`type ReconcileStatus = *reconcileStatus`). `nil` is a valid value meaning "success, no reschedule", and all methods are safe to call on a nil receiver. + ```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 +nil // Success, no reschedule +progress.WrapError(err) // Wrap error for handling +reconcileStatus.WithRequeue(5*time.Second) // Schedule reconcile after delay +reconcileStatus.WithProgressMessage("...") // Add progress message +progress.NeedsRefresh() // Immediate re-reconcile to refresh status after mutation +progress.WaitingOnOpenStack(progress.WaitingOnReady, 15*time.Second) // Poll for OpenStack state change +progress.WaitingOnObject("Network", name, progress.WaitingOnCreation) // Wait for a k8s object +reconcileStatus.WithReconcileStatus(other) // Merge two ReconcileStatuses ``` ### Error Classification @@ -142,14 +158,49 @@ if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { projectID := ptr.Deref(project.Status.ID, "") ``` +### Lightweight Dependency Lookup (FetchDependency) + +For one-off lookups that don't need finalizers (e.g., resolving refs in `ListOSResourcesForAdoption` or import filters), use `dependency.FetchDependency` instead of a declared dependency: + +```go +import "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + +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) +``` + +### Credentials Dependency (generated) + +Every controller has a `credentialsDependency` auto-generated in `zz_generated.controller.go`. It is a `DeletionGuardDependency` on `corev1.Secret` that ensures the cloud credentials secret exists and carries the controller's finalizer. It is checked in `newActuator()` before creating an OpenStack client: + +```go +_, reconcileStatus := credentialsDependency.GetDependencies( + ctx, controller.GetK8sClient(), orcObject, + func(*corev1.Secret) bool { return true }, +) +if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return myActuator{}, reconcileStatus +} +``` + +The credential watch is registered in `SetupWithManager` via `credentials.AddCredentialsWatch()`. + ## Common Patterns -### Resource Name Helper +### Resource Name Helper (generated) + +`getResourceName` is auto-generated in `zz_generated.adapter.go` — do not write it manually. It returns `spec.resource.name` if set, otherwise falls back to the ORC object's Kubernetes name: ```go -func getResourceName(orcObject *orcv1alpha1.Flavor) string { +// In zz_generated.adapter.go (DO NOT EDIT) +func getResourceName(orcObject orcObjectPT) string { if orcObject.Spec.Resource.Name != nil { - return *orcObject.Spec.Resource.Name + return string(*orcObject.Spec.Resource.Name) } return orcObject.Name } @@ -173,6 +224,59 @@ var _ createResourceActuator = flavorActuator{} var _ deleteResourceActuator = flavorActuator{} ``` +### Actuator Factory (newActuator) + +Every controller defines a `newActuator()` function that resolves credentials, creates the OpenStack client scope, and returns the actuator. This is called by the `helperFactory` methods `NewCreateActuator` and `NewDeleteActuator`: + +```go +func newActuator(ctx context.Context, orcObject *orcv1alpha1.Flavor, controller generic.ResourceController) (flavorActuator, 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 flavorActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject( + ctx, controller.GetK8sClient(), log, orcObject, + ) + if err != nil { + return flavorActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewComputeClient() // or NewNetworkClient, etc. + if err != nil { + return flavorActuator{}, progress.WrapError(err) + } + + return flavorActuator{osClient: osClient}, nil +} +``` + +### Tag Reconciliation (Neutron resources) + +Neutron resources use a separate tags API instead of the resource's Update API. The `internal/util/tags` package provides a reusable reconciler: + +```go +import "github.com/k-orc/openstack-resource-controller/v2/internal/util/tags" + +func (actuator myActuator) GetResourceReconcilers(...) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + tags.ReconcileTags[orcObjectPT, osResourceT]( + orcObject.Spec.Resource.Tags, + osResource.Tags, + tags.NewNeutronTagReplacer(actuator.osClient, "security-groups", osResource.ID), + ), + actuator.updateRules, + }, nil +} +``` + +`ReconcileTags` computes the diff between desired and observed tags and replaces them atomically. For resources whose tags are set via the standard Update API (e.g., block storage), use a `handleTagsUpdate()` helper in `updateResource` instead. + ### Pointer Handling ```go @@ -202,9 +306,9 @@ type ServerResourceSpec struct { 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 { +// Some resources are fully immutable (rare - e.g., ServerGroup) +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ServerGroupResourceSpec is immutable" +type ServerGroupResourceSpec struct { // ... } ``` @@ -250,11 +354,53 @@ make test-e2e # Run KUTTL E2E tests (requires E2E_OSCLOUDS) make fmt # Format code ``` +## Reconciler Naming Conventions + +`GetResourceReconcilers` returns a list of reconciler functions. There are two types: + +1. **`updateResource`**: Handles general mutable field updates via the resource's Update API. Uses `handleXXXUpdate()` helpers to build an `UpdateOpts` struct, then makes a single API call. Returns a terminal error when `spec.resource` is nil. Examples: securitygroup, volumetype, trunk, router. + +2. **Single-concern reconcilers**: Handle a specific aspect of the resource using a separate API (not the resource's Update API). Named with a descriptive verb+noun (e.g., `reconcileExtraSpecs`, `reconcileSubports`, `reconcilePassword`, `updateRules`). Return `nil` (not a terminal error) when `spec.resource` is nil. May make multiple API calls within a single reconciler. + +```go +// updateResource pattern - general mutable fields via Update API +func (actuator myActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + resource := obj.Spec.Resource + if resource == nil { + // Terminal error: updateResource is only registered for managed resources + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) + } + // ... build UpdateOpts, make single Update API call +} + +// Single-concern reconciler pattern - separate API +func (actuator myActuator) reconcileExtraSpecs(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + resource := obj.Spec.Resource + if resource == nil { + return nil // Not a terminal error + } + // ... compute diff, make API calls (creates, deletes, etc.) +} +``` + +Both types are registered in `GetResourceReconcilers`: +```go +func (actuator myActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller generic.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, // general field updates + actuator.reconcileExtraSpecs, // single-concern reconciler + }, nil +} +``` + ## Reference Controllers -- **Simple**: `internal/controllers/flavor/` - No dependencies, immutable +- **Simple**: `internal/controllers/servergroup/` - No dependencies, fully immutable +- **Single-concern reconciler**: `internal/controllers/flavor/` - No dependencies, immutable except extra specs (`reconcileExtraSpecs`) - **With dependencies**: `internal/controllers/securitygroup/` - Project dependency, rules reconciliation -- **Complex**: `internal/controllers/server/` - Multiple dependencies, reconcilers +- **Multiple reconcilers**: `internal/controllers/trunk/` - `updateResource` + `reconcileSubports` + tags +- **Complex**: `internal/controllers/server/` - Multiple dependencies, many reconcilers ## Documentation From 368d6fb3e473a9a1dbdd6ca5f45cdf07ea0748a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 30 Jun 2026 09:23:08 +0200 Subject: [PATCH 207/237] Skills: Fix inaccuracies, add cross-references, and remove redundancy --- .agents/skills/add-dependency/SKILL.md | 14 +-- .agents/skills/new-controller/SKILL.md | 23 +++- .agents/skills/new-controller/patterns.md | 40 +------ .agents/skills/proposal/SKILL.md | 71 ++---------- .agents/skills/testing/SKILL.md | 10 +- .agents/skills/update-controller/SKILL.md | 130 +++++++++------------- 6 files changed, 95 insertions(+), 193 deletions(-) diff --git a/.agents/skills/add-dependency/SKILL.md b/.agents/skills/add-dependency/SKILL.md index f657ab8f7..9f4d64b84 100644 --- a/.agents/skills/add-dependency/SKILL.md +++ b/.agents/skills/add-dependency/SKILL.md @@ -19,7 +19,7 @@ Use a dependency when your controller needs to: ## Key Principles -See also "Dependency Timing" in @.agents/skills/new-controller/patterns.md +See also "Dependency Timing" in [patterns.md](../new-controller/patterns.md) ### 1. Resolve Dependencies Late @@ -156,6 +156,8 @@ func (c myReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl. In `actuator.go`, resolve the dependency before using it: +Use `orcv1alpha1.IsAvailable` as the readiness predicate. This is sufficient because `Status.ID` is always set before a resource becomes Available: + ```go func (actuator myActuator) CreateResource(ctx context.Context, obj *orcv1alpha1.MyResource) (*osResourceT, progress.ReconcileStatus) { resource := obj.Spec.Resource @@ -164,9 +166,7 @@ func (actuator myActuator) CreateResource(ctx context.Context, obj *orcv1alpha1. 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 - }, + orcv1alpha1.IsAvailable, ) if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus @@ -189,9 +189,7 @@ func (actuator myActuator) ListOSResourcesForImport(ctx context.Context, obj orc 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 - }, + orcv1alpha1.IsAvailable, ) reconcileStatus = reconcileStatus.WithReconcileStatus(rs) @@ -235,7 +233,7 @@ 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. +Follow [testing](../testing/SKILL.md) for running unit tests, linting, and E2E tests. ## Checklist diff --git a/.agents/skills/new-controller/SKILL.md b/.agents/skills/new-controller/SKILL.md index af8ef4be7..c327c3fe4 100644 --- a/.agents/skills/new-controller/SKILL.md +++ b/.agents/skills/new-controller/SKILL.md @@ -201,9 +201,28 @@ Implement: - `ListOSResourcesForAdoption()` - Match by spec fields - `GetResourceReconcilers()` - (if resource supports updates) +**ReconcileResourceActuator is optional**: The generic reconciler detects it via type assertion at runtime — there is no factory method to implement. To opt in, add the `reconcileResourceActuator` type alias and interface assertion in `actuator.go`, then implement `GetResourceReconcilers` on the actuator struct: + +```go +type ( + reconcileResourceActuator = interfaces.ReconcileResourceActuator[orcObjectPT, osResourceT] + resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] +) + +var _ reconcileResourceActuator = myActuator{} + +func (actuator myActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} +``` + +If the resource is fully immutable (no mutable fields, no tags, no sub-resources), skip this entirely — the generic reconciler will not call it. + ### Implementation Patterns -Follow the patterns in @.agents/skills/new-controller/patterns.md when implementing the actuator and API types. +Follow the patterns in [patterns.md](patterns.md) when implementing the actuator and API types. ### Status Writer (internal/controllers//status.go) @@ -217,7 +236,7 @@ Implement: 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 +Complete the E2E test stubs in `internal/controllers//tests/` and run tests following [testing](../testing/SKILL.md) ## Checklist diff --git a/.agents/skills/new-controller/patterns.md b/.agents/skills/new-controller/patterns.md index e26c60945..b96bfe983 100644 --- a/.agents/skills/new-controller/patterns.md +++ b/.agents/skills/new-controller/patterns.md @@ -85,49 +85,17 @@ Distinguish between errors that can be retried vs those requiring user action. | **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 on create: User must fix the spec -if err != nil { - if !orcerrors.IsRetryable(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "invalid configuration creating resource: "+err.Error(), err) - } - return nil, progress.WrapError(err) -} - -// Terminal on update: Treat as terminal (spec likely conflicts with existing state) -if err != nil { - if !orcerrors.IsRetryable(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "invalid configuration updating resource: "+err.Error(), err) - } - return progress.WrapError(err) -} -``` +Use `orcerrors.IsRetryable(err)` to check; wrap non-retryable errors with `orcerrors.Terminal()`. See AGENTS.md "Error Classification" for the code pattern. ## 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. +Resolve dependencies as late as possible, as close to the point of use as possible. Only fetch a dependency when you actually need its ID for the current operation. -**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 -``` +- Only fetch optional dependencies conditionally (`if resource.SubnetRef != nil`) -For detailed dependency implementation: @.agents/skills/add-dependency/SKILL.md +For detailed implementation: [add-dependency](../add-dependency/SKILL.md) ## 6. Code Clarity diff --git a/.agents/skills/proposal/SKILL.md b/.agents/skills/proposal/SKILL.md index 212fadd35..b735c31a6 100644 --- a/.agents/skills/proposal/SKILL.md +++ b/.agents/skills/proposal/SKILL.md @@ -91,45 +91,12 @@ Before writing a proposal, ask the user about: ## Research Phase -Before writing the proposal, research relevant areas based on the enhancement type: +Before writing the proposal, research the relevant area: -### 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? +- **OpenStack API & gophercloud**: Read the API docs, check gophercloud support, note async operations +- **Existing ORC patterns**: Look at similar controllers or infrastructure code for patterns to follow +- **Dependencies**: Map ORC resource dependencies (required vs optional, deletion guards) +- **Current implementation**: For infrastructure enhancements, read the actual code to verify technical details (ports, endpoints, framework capabilities) ## Filling Out the Template @@ -194,33 +161,9 @@ Address each of these in the **Risks and Edge Cases** section: | **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. +### Before Submitting -5. **Keep it updated** - As implementation progresses, update the Implementation History section. +Verify internal consistency (anything referenced in one section is defined elsewhere), technical accuracy (check against actual code), and that examples are complete. ## Submission Process diff --git a/.agents/skills/testing/SKILL.md b/.agents/skills/testing/SKILL.md index 88919c1f7..317eb9998 100644 --- a/.agents/skills/testing/SKILL.md +++ b/.agents/skills/testing/SKILL.md @@ -20,9 +20,13 @@ 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 +E2E tests require `E2E_OSCLOUDS` environment variable pointing to a `clouds.yaml` file containing cloud entries for regular and admin credentials. The cloud names are configurable via environment variables: + +| Variable | Description | Default | +|----------|-------------|--------| +| `E2E_OSCLOUDS` | Path to `clouds.yaml` | `/etc/openstack/clouds.yaml` | +| `E2E_OPENSTACK_CLOUD_NAME` | Cloud name for regular credentials | `devstack` | +| `E2E_OPENSTACK_ADMIN_CLOUD_NAME` | Cloud name for admin credentials | `devstack-admin-demo` | 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. diff --git a/.agents/skills/update-controller/SKILL.md b/.agents/skills/update-controller/SKILL.md index e7d59fe0a..f80d9601f 100644 --- a/.agents/skills/update-controller/SKILL.md +++ b/.agents/skills/update-controller/SKILL.md @@ -31,7 +31,7 @@ Research the resource before implementing changes: ## Key Principles -When updating controllers, follow the patterns in @.agents/skills/new-controller/patterns.md +When updating controllers, follow the patterns in [patterns.md](../new-controller/patterns.md) ## Common Update Scenarios @@ -81,27 +81,13 @@ When updating controllers, follow the patterns in @.agents/skills/new-controller 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 - } - } - ``` +3. Add update handling to the `updateResource()` reconciler (or create it if not present). Follow the pattern in `internal/controllers/securitygroup/actuator.go` (or `trunk/actuator.go`): + - Build an `UpdateOpts` struct using `handleXXXUpdate()` helpers for each mutable field + - Use a `needsUpdate()` helper that serializes the opts to a map and checks `len() > 0` + - Call the Update API only if something changed, return `progress.NeedsRefresh()` + - Return terminal error if `spec.resource` is nil - **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). + **Note**: Only use `updateResource` when the field is updated via the resource's standard Update API. If the field requires a different API (e.g., extra specs, subports, tags on networking resources), create a separate single-concern reconciler instead. See [Adding a Single-Concern Reconciler](#adding-a-single-concern-reconciler) below. 4. Register in `GetResourceReconcilers()`: ```go @@ -110,9 +96,45 @@ When updating controllers, follow the patterns in @.agents/skills/new-controller }, nil ``` +### Adding a Single-Concern Reconciler + +When a mutable field uses a separate OpenStack API (not the resource's Update API), create a dedicated reconciler with a descriptive verb+noun name instead of adding logic to `updateResource`. + +**Examples**: `reconcileExtraSpecs` (flavor, volumetype), `reconcileSubports` (trunk), `reconcilePassword` (user), `updateRules` (securitygroup). + +Key differences from `updateResource`: + +- **Naming**: Use a descriptive name (e.g., `reconcileExtraSpecs`), not `updateResource`. +- **Nil guard**: Return `nil` when `spec.resource` is nil (not a terminal error). The terminal error pattern is reserved for `updateResource`. +- **Multiple API calls**: A single-concern reconciler may make multiple API calls (e.g., create some extra specs, delete others). This is an established pattern (see `reconcileSubports`, `updateRules`). +- **Idempotency**: Operations must be idempotent. If the reconciler fails partway through, the next reconciliation recomputes the diff from the current OpenStack state and retries only what's still needed. + +```go +func (actuator myActuator) reconcileExtraSpecs(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + resource := obj.Spec.Resource + if resource == nil { + return nil // Not a terminal error (unlike updateResource) + } + + // Compute desired vs current diff + // Make API calls (creates, updates, deletes) + // Return progress.NeedsRefresh() if any changes were made +} +``` + +Register alongside other reconcilers in `GetResourceReconcilers()`: +```go +return []resourceReconciler{ + actuator.updateResource, // general field updates via Update API + actuator.reconcileExtraSpecs, // single-concern: separate API +}, nil +``` + +**Do NOT duplicate work in `CreateResource`**. If a reconciler handles a concern (e.g., extra specs), do not also set that data in `CreateResource`. The `CreateResource` contract forbids actions that can fail after creating the primary resource. The reconciler will handle it on the first reconciliation after creation. + ### Adding a Dependency -See @.agents/skills/add-dependency/SKILL.md for detailed steps. +See [add-dependency](../add-dependency/SKILL.md) for detailed steps. ### Improving DeleteResource @@ -156,43 +178,11 @@ func (actuator myActuator) DeleteResource(ctx context.Context, _ orcObjectPT, re 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 - } - } - ``` +2. Sort tags before creation and comparison (use `slices.Sort` — see `patterns.md` §3 Deterministic State). -4. Register in `GetResourceReconcilers()`: - ```go - return []resourceReconciler{ - actuator.updateResource, // includes handleTagsUpdate - }, nil - ``` +3. Add a `handleTagsUpdate()` helper that sorts both desired and current tags, compares with `slices.Equal`, and sets `updateOpts.Tags` only if different. Copy before sorting to avoid mutating the original. -**Note**: Import `"slices"` for sorting/comparison functions. +4. Register `updateResource` (which calls `handleTagsUpdate`) in `GetResourceReconcilers()`. ### Adding Status Constants @@ -211,35 +201,15 @@ const ( ) ``` -See also `@.agents/skills/new-controller/patterns.md` for more details on this pattern. +See also [patterns.md](../new-controller/patterns.md) for more details on this pattern. ### Improving Error Handling -Ensure proper error classification: - -```go -// Terminal on create: Invalid configuration - user must fix spec -if err != nil { - if !orcerrors.IsRetryable(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "invalid configuration creating resource: "+err.Error(), err) - } - return nil, progress.WrapError(err) -} - -// Terminal on update: -if err != nil { - if !orcerrors.IsRetryable(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "invalid configuration updating resource: "+err.Error(), err) - } - return progress.WrapError(err) -} -``` +See [patterns.md](../new-controller/patterns.md) §4 Error Classification. Wrap non-retryable errors with `orcerrors.Terminal`; leave transient errors as-is for automatic retry. ## Testing Changes -Follow @.agents/skills/testing/SKILL.md for running unit tests, linting, and E2E tests. +Follow [testing](../testing/SKILL.md) for running unit tests, linting, and E2E tests. ## Checklist From e5dfbe0fe12084302717bd4eed801ff0733294c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 30 Jun 2026 09:23:11 +0200 Subject: [PATCH 208/237] Skills: Add review skill for ORC controller code review --- .agents/skills/review/SKILL.md | 320 +++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 .agents/skills/review/SKILL.md diff --git a/.agents/skills/review/SKILL.md b/.agents/skills/review/SKILL.md new file mode 100644 index 000000000..fc235d3c8 --- /dev/null +++ b/.agents/skills/review/SKILL.md @@ -0,0 +1,320 @@ +--- +name: review +description: Review ORC controller code for Kubernetes best practices and ORC conventions. Use after implementing or modifying a controller. +disable-model-invocation: true +--- + +# ORC Code Review Guide + +Review ORC controller code for correctness, Kubernetes best practices, and ORC conventions. Produce a structured report at the end. + +## Step 1: Identify Review Scope + +Determine what changed and which checklists apply: + +1. Run `git diff` (or `git diff --cached`, or diff against the base branch) to identify modified files. +2. Categorize changes by file type: + - `api/v1alpha1/*_types.go` -> API Types checklist + - `internal/controllers/*/controller.go` -> Controller Setup checklist + - `internal/controllers/*/actuator.go` -> Actuator Logic checklist + - `internal/controllers/*/status.go` -> Status Writer checklist + - `internal/controllers/*/tests/` -> Test Coverage checklist + - Any `.go` file -> Code Style checklist +3. Always apply the Kubernetes Best Practices checklist. +4. Read every changed file in full before reviewing. Also read surrounding context (e.g., the full `*_types.go` for the resource, even if only part changed). + +## Step 2: API Types (`*_types.go`) + +Review any `api/v1alpha1/*_types.go` file against these rules: + +### Structure + +- [ ] Three hand-written types exist: `ResourceSpec`, `Filter`, `ResourceStatus`. +- [ ] Top-level types (``, `Spec`, `Status`, `List`) are code-generated in `zz_generated.*` files and NOT hand-edited. +- [ ] `ResourceSpec` contains fields mapping to OpenStack create API parameters. +- [ ] `Filter` contains a subset of identifying fields, all optional pointers. +- [ ] `ResourceStatus` contains observed state from OpenStack. + +### Validation Markers + +- [ ] String fields use `+kubebuilder:validation:MinLength` / `MaxLength` constraints. +- [ ] Numeric fields use `+kubebuilder:validation:Minimum` / `Maximum` where appropriate. +- [ ] Enum types use `+kubebuilder:validation:Enum` listing all valid values. +- [ ] Filter structs have `+kubebuilder:validation:MinProperties:=1` (at least one criterion required). +- [ ] Slice fields have `+listType` annotations (`set` for unique items like tags, `atomic` for ordered/opaque lists, `map` with `+listMapKey` for keyed lists like conditions). + +### Immutability + +- [ ] Fully immutable resources (rare, e.g., ServerGroup) apply `+kubebuilder:validation:XValidation:rule="self == oldSelf"` at the struct level. +- [ ] Partially mutable resources apply `rule="self == oldSelf"` on individual immutable fields, leaving mutable fields unmarked. +- [ ] Immutability validation messages are descriptive (e.g., `"imageRef is immutable"`). + +### Field Conventions + +- [ ] `+required` fields use value types (e.g., `RAM int32`) but still have `json:"...,omitempty"`. +- [ ] `+optional` fields use pointer types (e.g., `*OpenStackName`, `*bool`) to distinguish "not set" from zero. +- [ ] In `ResourceStatus`, fields are `+optional` with pointers or plain strings with `omitempty`. +- [ ] Status string fields use `string` with `+kubebuilder:validation:MaxLength=1024`, not the strongly-typed wrapper. + +### Shared Types + +- [ ] Resource names use `OpenStackName` (not raw `string`). Check the specific OpenStack project for the correct max length (Keystone: 64 chars, Neutron: 255 chars, etc.). +- [ ] References to other ORC objects use `KubernetesNameRef` with a `Ref` suffix (e.g., `projectRef`, `networkRef`). +- [ ] References NEVER point to raw OpenStack resource IDs. OpenStack IDs appear only in status. +- [ ] IP addresses use `IPvAny`, CIDRs use `CIDR`, MACs use `MAC`. +- [ ] Neutron resources use shared types: `NeutronDescription`, `NeutronTag`, `FilterByNeutronTags`, `NeutronStatusMetadata`. +- [ ] Non-Neutron resources define their own tag types with appropriate length constraints. + +### Sub-resources + +- [ ] Nested sub-resources have separate Spec and Status types (e.g., `SecurityGroupRule` vs `SecurityGroupRuleStatus`). +- [ ] Sub-resource Status types include an `ID` field when the sub-resource has its own OpenStack ID. +- [ ] Complex cross-field validation uses `XValidation` rules on the sub-resource struct. + +## Step 3: Controller Setup (`controller.go`) + +### Basic Setup + +- [ ] RBAC markers are present and minimal (only the verbs actually needed). +- [ ] Controller name is lowercase, may contain hyphens, and is unique across all controllers. +- [ ] `GetName()` returns the controller name constant. +- [ ] `SetupWithManager` follows the standard pattern: builder -> watches -> dependency registration -> reconciler creation -> complete. + +### Dependencies + +- [ ] Dependencies are declared as **package-level variables**, not inside functions. +- [ ] `DeletionGuardDependency` is used when deleting the dependency would either fail or cause the dependent to fail. +- [ ] Regular `Dependency` (no deletion guard) is used for import-only dependencies and cases where OpenStack allows the deletion. +- [ ] Each dependency has a descriptive name (e.g., `vipSubnetDependency` not `subnetDependency` when multiple subnet types exist). +- [ ] Field path strings in dependency declarations match the actual API field paths. +- [ ] Extraction functions correctly handle nil checks for optional references. + +### Watches + +- [ ] Each dependency has a corresponding `Watches` call in `SetupWithManager`. +- [ ] Watch handlers use `predicates.NewBecameAvailable` to avoid unnecessary reconciles. +- [ ] Credential dependency watch is always registered. +- [ ] All dependency registrations use `errors.Join` with `AddToManager`. + +## Step 4: Actuator Logic (`actuator.go`) + +### Structure + +- [ ] Type aliases defined at the top of the file for `osResourceT`, actuator interfaces, and `helperFactory`. +- [ ] Compile-time interface assertions present (`var _ createResourceActuator = myActuator{}`). +- [ ] OS client interface defined locally with only the methods the actuator needs. +- [ ] Actuator struct holds the OS client and optionally `k8sClient` (when dependencies are used). + +### Resource Name + +- [ ] `getResourceName` helper exists: returns `spec.resource.name` if set, otherwise falls back to the ORC object name. + +### GetOSResourceByID + +- [ ] Wraps errors with `progress.WrapError`. +- [ ] Handles "not found" correctly (returns `nil` resource, not an error). + +### ListOSResourcesForAdoption + +- [ ] Returns `false` (second return value) when `spec.resource` is nil (no spec to match against). +- [ ] Builds client-side filters matching the **full** resource spec for accurate adoption. + +### ListOSResourcesForImport + +- [ ] Builds filters from the import filter spec only. +- [ ] All filter fields are mapped. + +### CreateResource + +- [ ] Translates ORC spec into OpenStack `CreateOpts` completely. +- [ ] **MUST NOT** perform any action after the Create API call (idempotency requirement). +- [ ] Any actions before Create are idempotent (Create may be called many times). +- [ ] Non-retryable errors are wrapped with `orcerrors.Terminal`. +- [ ] Lists (tags, etc.) are sorted before passing to Create for deterministic state. +- [ ] Finalizers on dependencies are added immediately before the Create call, not earlier. + +### DeleteResource + +- [ ] **MUST NOT** perform any action after the Delete API call. +- [ ] Handles "not found" gracefully (resource already deleted). +- [ ] For resources with intermediate states: checks provisioning status before deleting, handles 409 Conflict by waiting. +- [ ] Minimal dependency requirements -- does not require dependencies that aren't strictly needed for deletion. + +### ReconcileResourceActuator (if implemented) + +- [ ] `GetResourceReconcilers` returns reconciler functions for post-creation tasks (e.g., setting Neutron tags, handling mutable field updates). +- [ ] Reconcilers that modify the OpenStack resource return a `progress.ProgressStatus` to force a status refresh. +- [ ] Reconcilers are independent and don't rely on side effects of other reconcilers. +- [ ] `updateResource` is used only for general mutable field updates via the resource's Update API (building `UpdateOpts`, single API call). Operations using a separate API have a descriptive name (e.g., `reconcileExtraSpecs`, `reconcileSubports`, `reconcilePassword`, `updateRules`). +- [ ] Single-concern reconcilers return `nil` (not a terminal error) when `spec.resource` is nil. Only `updateResource` returns a terminal error for nil `spec.resource`. +- [ ] `CreateResource` does not duplicate work that is handled by a reconciler. The `CreateResource` contract forbids actions that can fail after creating the primary resource. + +### Error Handling + +- [ ] All errors from OpenStack API calls are checked. +- [ ] Non-retryable errors (400, invalid config) wrapped with `orcerrors.Terminal` and an appropriate `ConditionReason`. +- [ ] Transient errors (5xx, network) left as default (automatic retry with backoff). +- [ ] `ReconcileStatus` return values are **never discarded** -- always assigned and propagated. +- [ ] When wrapping errors, use `progress.WrapError(err)` (not bare `fmt.Errorf`). + +### Dependency Resolution + +- [ ] Dependencies resolved **as late as possible**, close to the point of use. +- [ ] Dependencies not required for deletion unless strictly necessary (e.g., don't require Network to delete a Subnet with `status.ID` already set). +- [ ] Dependencies not required for import-by-ID. +- [ ] `GetDependency` results checked: if `needsReschedule` is true, return early. +- [ ] Readiness predicate uses `orcv1alpha1.IsAvailable` (the standard helper from `api/v1alpha1/conditions.go`). `Status.ID` is always set before a resource becomes Available, so checking `dep.Status.ID != nil` separately is unnecessary. + +## Step 5: Status Writer (`status.go`) + +### Structure + +- [ ] Type aliases for `objectApplyT` and `statusApplyT` (SSA apply configuration types). +- [ ] Compile-time interface assertion for `ResourceStatusWriter`. + +### ResourceAvailableStatus + +- [ ] Returns `ConditionTrue` only when the resource is completely ready for use. +- [ ] Returns `ConditionFalse` when `osResource` is nil and no `status.ID` exists (not yet created). +- [ ] Returns `ConditionUnknown` when `osResource` is nil but `status.ID` exists (can't verify current state). +- [ ] For resources with intermediate states (BUILD, PENDING_CREATE): returns `ConditionFalse` until the resource reaches a stable, usable state (e.g., ACTIVE). +- [ ] For resources in ERROR state: returns `ConditionFalse`. + +### ApplyResourceStatus + +- [ ] Maps **all** OpenStack resource fields to ORC status fields. +- [ ] Zero/empty values handled correctly: only include swap, ephemeral, description, etc., when non-zero/non-empty. +- [ ] Does NOT attempt to preserve previous status when the OpenStack resource can't be fetched (status.resource is cleared intentionally). +- [ ] Pointer fields in status use `ptr.To()` for conversion. + +## Step 6: Kubernetes Best Practices + +### Conditions + +- [ ] **Progressing=True** means status doesn't yet reflect spec AND controller expects more reconciles. +- [ ] **Progressing=False** means the object will NOT be reconciled again until the spec changes. This covers both success (Available=True) and terminal errors. +- [ ] **Available=True** means the resource is ready for use by consumers. +- [ ] Condition reasons use defined constants from `orcv1alpha1` (e.g., `ConditionReasonInvalidConfiguration`, `ConditionReasonTransientError`). +- [ ] Conditions are not set directly by the actuator -- the generic reconciler handles this based on `ReconcileStatus` and `ResourceStatusWriter` return values. + +### Finalizers + +- [ ] Finalizers on dependency objects are added only immediately before the OpenStack create/update call that references them (not during initialization). +- [ ] Deletion guard finalizers are managed by `DeletionGuardDependency` -- the controller doesn't manually add/remove them. +- [ ] The controller's own finalizer is managed by the generic reconciler framework. + +### Server-Side Apply + +- [ ] Status is written via SSA apply configurations (not direct status updates). +- [ ] `GetApplyConfig` returns a fresh apply configuration each time. +- [ ] Status is written in a single SSA transaction per reconcile. + +### Resource Safety + +- [ ] No cascade deletes unless the user explicitly requested them. +- [ ] No auto-correction of invalid states that might cause data loss. +- [ ] Prefer failing safely over making assumptions. + +## Step 7: Code Style + +See AGENTS.md for conventions (import ordering, logging levels, pointer handling). Only flag deviations that affect correctness: + +- [ ] Generated files (`zz_generated.*`) are not hand-edited. +- [ ] `make generate` has been run after any API type changes. +- [ ] Constants from gophercloud are preferred over locally defined string constants (e.g., `ports.StatusActive` instead of `"ACTIVE"`). + +## Step 8: Test Coverage + +### E2E Test Directories + +For each controller, verify the following test directories exist under `internal/controllers//tests/`: + +| Required Test | Purpose | +|---------------|---------| +| `-create-minimal/` | Create with only required fields, verify status matches | +| `-create-full/` | Create with all fields populated | +| `-import/` | Import an existing OpenStack resource | +| `-import-error/` | Import with no matches, verify error handling | +| `-dependency/` | Test dependency waiting and deletion guard protection | + +| Conditional Test | When Required | +|------------------|---------------| +| `-update/` | Resource has mutable fields | +| `-import-dependency/` | Import filter references other ORC objects | + +### E2E Test Quality + +- [ ] Each test directory has a `README.md` describing each step. +- [ ] Step files use zero-padded numeric prefixes (`00-`, `01-`, etc.). +- [ ] Cloud credentials secret created via `TestStep` command (not a manifest) using `E2E_KUTTL_OSCLOUDS`. +- [ ] Assertions verify `status.resource` fields match the spec. +- [ ] Conditions (`Available`, `Progressing`) are asserted with correct `status`, `reason`, and `message`. +- [ ] Dependency tests verify: (1) waiting state with `Progressing=True`, (2) availability after dep created, (3) finalizer blocks dep deletion, (4) dep deleted after resource deleted. +- [ ] Update tests use `kubectl replace` (not KUTTL patch) to test field removal. +- [ ] CEL expressions (`celExpr`) used for complex assertions (e.g., checking `deletionTimestamp`, finalizer membership, field absence with `!has(...)`). + +### Unit / API Validation Tests + +- [ ] API validation tests exist at `test/apivalidations/_test.go` for non-trivial validation rules. +- [ ] Unit tests cover any complex helper logic. + +## Step 9: Produce Review Report + +After running through all applicable checklists, produce a structured report: + +### Report Format + +``` +## Review Summary + +**Scope**: +**Overall**: + +## Blockers +Items that MUST be fixed before merge. These are correctness issues, violations +of idempotency/safety invariants, or missing required functionality. + +- [file:line] Description of the issue and why it's a blocker. + +## Warnings +Items that SHOULD be fixed. These are convention violations, missing edge case +handling, or patterns that may cause issues in production. + +- [file:line] Description and recommendation. + +## Suggestions +Items that COULD be improved. Style preferences, minor optimizations, or +additional test coverage that would be nice to have. + +- [file:line] Description and suggestion. + +## Positive Observations +Notable good practices observed in the code (keep brief, 2-3 items max). +``` + +### Severity Guidelines + +**Blocker** -- any of: +- Violates CreateResource/DeleteResource idempotency invariant (actions after the API call) +- Incorrect Progressing/Available condition semantics (could cause reconciliation to hang) +- Missing `ReconcileStatus` propagation (discarded return value) +- Terminal error not marked terminal (infinite retry of unfixable error) +- Missing finalizer or finalizer added too early +- Security issue (RBAC too broad, secrets leaked in logs) +- Data loss risk (cascade delete without explicit user intent) + +**Warning** -- any of: +- Missing validation markers on API types +- Dependency resolved too early (unnecessary coupling) +- Missing error wrapping (`progress.WrapError`) +- Incomplete status mapping (fields not reflected in status) +- Missing E2E test for a standard scenario +- Wrong logging level +- Missing interface assertion + +**Suggestion** -- any of: +- Import ordering +- Naming could be more descriptive +- Additional test coverage beyond the standard set +- Code could be simplified +- Comment could be clearer From aa3a4761e4be2702404675011f5fee1c35f2c372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 30 Jun 2026 09:58:47 +0200 Subject: [PATCH 209/237] Bump golang.org/x/net This fixes multiple vulnerabilities. --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2ef833a01..9249355f0 100644 --- a/go.mod +++ b/go.mod @@ -85,10 +85,10 @@ 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.36.0 // indirect - golang.org/x/net v0.54.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect golang.org/x/term v0.43.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.45.0 // indirect diff --git a/go.sum b/go.sum index fdd0cf7bb..bda98a1d0 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.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= 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,8 +225,8 @@ golang.org/x/sync v0.21.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.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From b4c29f50145125b69439e65d3ec7af00e171a92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 30 Jun 2026 14:09:31 +0200 Subject: [PATCH 210/237] Bump gophercloud to v2.13.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9249355f0..e7597c3b9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,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.12.0 + github.com/gophercloud/gophercloud/v2 v2.13.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 github.com/onsi/ginkgo/v2 v2.31.0 github.com/onsi/gomega v1.42.0 diff --git a/go.sum b/go.sum index bda98a1d0..c94d79f96 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oX github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/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.12.0 h1:Gxmc/Bog1UDKkxTcQW7MSPTDviJXpLeEgVeN5KrxoCo= -github.com/gophercloud/gophercloud/v2 v2.12.0/go.mod h1:H7TTOxbLy8RIaHSNhI2GCrWIzw4Xpw8Xn2mBhCUT5kA= +github.com/gophercloud/gophercloud/v2 v2.13.0 h1:yEyJG+kABd8x2ttTqLsomihU6Kg2YheJSZhvP/QSx+8= +github.com/gophercloud/gophercloud/v2 v2.13.0/go.mod h1:KZRLVs6gcoy/pEFdkZqFjdYqnS0emMHv66UqdM5lMjU= 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 b87ea7e74df2e82db160a5806df43c1754369ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 30 Jun 2026 14:58:58 +0200 Subject: [PATCH 211/237] Add .gitattributes to mark generated files Tag generated files so GitHub and other tools can exclude them. https://github.com/github-linguist/linguist/blob/main/docs/overrides.md --- .gitattributes | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..22eecab4f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,21 @@ +# Generated API types +api/v1alpha1/zz_generated.*.go linguist-generated + +# Generated OpenAPI schema +cmd/models-schema/zz_generated.openapi.go linguist-generated + +# Generated controller scaffolding +internal/controllers/**/zz_generated.*.go linguist-generated + +# Generated mock clients +internal/osclients/mock/*.go linguist-generated + +# Generated Kubernetes client libraries +pkg/clients/**/*.go linguist-generated + +# Generated CRD manifests +config/crd/bases/*.yaml linguist-generated + +# Generated documentation +website/docs/development/godoc/*.md linguist-generated +website/docs/crd-reference.md From 4992c73e15550d8fd02cd3e36271df93304f82e3 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Mon, 12 Jan 2026 11:34:31 -0300 Subject: [PATCH 212/237] port: add propagationUplinkStatus field --- .github/workflows/e2e.yaml | 2 +- api/v1alpha1/port_types.go | 9 +++++ api/v1alpha1/zz_generated.deepcopy.go | 5 +++ cmd/models-schema/zz_generated.openapi.go | 7 ++++ .../bases/openstack.k-orc.cloud_ports.yaml | 11 ++++++ internal/controllers/port/actuator.go | 15 +++---- internal/controllers/port/actuator_test.go | 1 - internal/controllers/port/status.go | 5 ++- .../port-create-full/00-create-resource.yaml | 1 + .../tests/port-create-minimal/00-assert.yaml | 2 +- .../tests/port-create-sriov/00-assert.yaml | 2 +- .../port/tests/port-update/00-assert.yaml | 2 +- .../port/tests/port-update/01-assert.yaml | 2 +- .../port/tests/port-update/02-assert.yaml | 2 +- internal/osclients/networking.go | 37 ++++++++++++++++++ .../api/v1alpha1/portresourcespec.go | 39 ++++++++++++------- .../applyconfiguration/internal/internal.go | 3 ++ website/docs/crd-reference.md | 1 + 18 files changed, 116 insertions(+), 30 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index ae7b58fbd..2922eacdf 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -38,7 +38,7 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server,neutron-trunk,neutron-port-trusted-vif" + enabled_services: "openstack-cli-server,neutron-trunk,neutron-port-trusted-vif,neutron-uplink-status-propagation" conf_overrides: | enable_plugin neutron https://github.com/openstack/neutron ${{ matrix.openstack_version }} enable_plugin manila https://github.com/openstack/manila ${{ matrix.openstack_version }} diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index f743acd16..108dc8c5f 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -244,6 +244,15 @@ type PortResourceSpec struct { // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="valueSpecs is immutable" ValueSpecs []PortValueSpec `json:"valueSpecs,omitempty"` + + // propagateUplinkStatus represents the uplink status propagation of + // the port. + // The field is now immutable due to a limitation on + // Dalmatian (2024.2) release, we should address this later. + // https://github.com/k-orc/openstack-resource-controller/pull/641#discussion_r2694783787 + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="propagateUplinkStatus is immutable" + PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` } type PortResourceStatus struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 2f4fe34aa..25a947114 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3386,6 +3386,11 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.PropagateUplinkStatus != nil { + in, out := &in.PropagateUplinkStatus, &out.PropagateUplinkStatus + *out = new(bool) + **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 bbdaee5f0..c702e616c 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -6419,6 +6419,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, }, }, + "propagateUplinkStatus": { + SchemaProps: spec.SchemaProps{ + Description: "propagateUplinkStatus represents the uplink status propagation of the port. The field is now immutable due to a limitation on Dalmatian (2024.2) release, we should address this later. https://github.com/k-orc/openstack-resource-controller/pull/641#discussion_r2694783787", + Type: []string{"boolean"}, + 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 8c38158c6..8212ec147 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -377,6 +377,17 @@ spec: x-kubernetes-validations: - message: projectRef is immutable rule: self == oldSelf + propagateUplinkStatus: + description: |- + propagateUplinkStatus represents the uplink status propagation of + the port. + The field is now immutable due to a limitation on + Dalmatian (2024.2) release, we should address this later. + https://github.com/k-orc/openstack-resource-controller/pull/641#discussion_r2694783787 + type: boolean + x-kubernetes-validations: + - message: propagateUplinkStatus is immutable + rule: self == oldSelf securityGroupRefs: description: |- securityGroupRefs are references to the security groups associated diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index cd4db8a5e..a602d2d69 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -249,13 +249,14 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha } createOpts := ports.CreateOpts{ - NetworkID: *network.Status.ID, - Name: getResourceName(obj), - Description: string(ptr.Deref(resource.Description, "")), - ProjectID: projectID, - AdminStateUp: resource.AdminStateUp, - MACAddress: resource.MACAddress, - ValueSpecs: valueSpecs, + NetworkID: *network.Status.ID, + Name: getResourceName(obj), + Description: string(ptr.Deref(resource.Description, "")), + ProjectID: projectID, + AdminStateUp: resource.AdminStateUp, + MACAddress: resource.MACAddress, + ValueSpecs: valueSpecs, + PropagateUplinkStatus: resource.PropagateUplinkStatus, } if len(resource.AllowedAddressPairs) > 0 { diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index cf31483a9..84d12768f 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -462,7 +462,6 @@ func TestHandleTrustedVIFUpdate(t *testing.T) { } updateOpts := handlePortTrustedVIFUpdate(&ports.UpdateOpts{}, resource, osResource) - got, _ := needsUpdate(updateOpts) if got != tt.expectChange { t.Errorf("expected needsUpdate=%v, got %v", tt.expectChange, got) diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index 9a8372569..f7e466971 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -67,7 +67,6 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou WithNetworkID(osResource.NetworkID). WithTags(osResource.Tags...). WithSecurityGroups(osResource.SecurityGroups...). - WithPropagateUplinkStatus(osResource.PropagateUplinkStatus). WithVNICType(osResource.VNICType). WithPortSecurityEnabled(osResource.PortSecurityEnabled). WithRevisionNumber(int64(osResource.RevisionNumber)). @@ -108,5 +107,9 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou resourceStatus.WithTrustedVIF(*osResource.PortTrustedVIF) } + if osResource.PropagateUplinkStatusPtr != nil { + resourceStatus.WithPropagateUplinkStatus(*osResource.PropagateUplinkStatusPtr) + } + statusApply.WithResource(resourceStatus) } 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 26e1d9d27..53ca4413b 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 @@ -87,3 +87,4 @@ spec: macAddress: fa:16:3e:23:fd:d7 hostID: id: devstack + propagateUplinkStatus: false diff --git a/internal/controllers/port/tests/port-create-minimal/00-assert.yaml b/internal/controllers/port/tests/port-create-minimal/00-assert.yaml index 9c6d861fc..ba68844fe 100644 --- a/internal/controllers/port/tests/port-create-minimal/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-minimal/00-assert.yaml @@ -8,7 +8,7 @@ status: name: port-create-minimal adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: false + propagateUplinkStatus: true revisionNumber: 1 status: DOWN vnicType: normal diff --git a/internal/controllers/port/tests/port-create-sriov/00-assert.yaml b/internal/controllers/port/tests/port-create-sriov/00-assert.yaml index 3dc6e78be..f5f0205c8 100644 --- a/internal/controllers/port/tests/port-create-sriov/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-sriov/00-assert.yaml @@ -9,7 +9,7 @@ status: description: Port from "create sriov" test adminStateUp: true portSecurityEnabled: false - propagateUplinkStatus: false + propagateUplinkStatus: true status: DOWN vnicType: direct tags: diff --git a/internal/controllers/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index 4987674c3..69cdc99e8 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -23,7 +23,7 @@ status: name: port-update adminStateUp: true portSecurityEnabled: false - propagateUplinkStatus: false + propagateUplinkStatus: true revisionNumber: 1 status: DOWN vnicType: normal diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index ee4510e2c..0fa512121 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -34,7 +34,7 @@ status: description: port-update-updated adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: false + propagateUplinkStatus: true status: DOWN vnicType: direct allowedAddressPairs: diff --git a/internal/controllers/port/tests/port-update/02-assert.yaml b/internal/controllers/port/tests/port-update/02-assert.yaml index a3e211d75..a96e5d05a 100644 --- a/internal/controllers/port/tests/port-update/02-assert.yaml +++ b/internal/controllers/port/tests/port-update/02-assert.yaml @@ -24,7 +24,7 @@ status: name: port-update adminStateUp: true portSecurityEnabled: false - propagateUplinkStatus: false + propagateUplinkStatus: true status: DOWN vnicType: normal conditions: diff --git a/internal/osclients/networking.go b/internal/osclients/networking.go index 088c9799c..a628d9146 100644 --- a/internal/osclients/networking.go +++ b/internal/osclients/networking.go @@ -18,6 +18,7 @@ package osclients import ( "context" + "encoding/json" "fmt" "iter" @@ -58,6 +59,41 @@ type PortExt struct { portsecurity.PortSecurityExt portsbinding.PortsBindingExt portstrustedvif.PortTrustedVIFExt + + // PropagateUplinkStatusPtr is a pointer variant of ports.Port.PropagateUplinkStatus. + // The embedded field is a non-pointer bool that defaults to false, making it + // impossible to distinguish "extension not enabled" from "explicitly false". + // This pointer allows detecting whether the field was present in the API response. + // It won't be needed with Gophercloud v3. + PropagateUplinkStatusPtr *bool `json:"propagate_uplink_status,omitempty"` +} + +// TODO(winiciusallan): Drop this custom unmarshaler once Gophercloud is +// on V3, so we have the following change +// https://github.com/gophercloud/gophercloud/pull/3609. +func (p *PortExt) UnmarshalJSON(b []byte) error { + if err := json.Unmarshal(b, &p.Port); err != nil { + return err + } + if err := json.Unmarshal(b, &p.PortSecurityExt); err != nil { + return err + } + if err := json.Unmarshal(b, &p.PortsBindingExt); err != nil { + return err + } + if err := json.Unmarshal(b, &p.PortTrustedVIFExt); err != nil { + return err + } + + var tmp struct { + PropagateUplinkStatusPtr *bool `json:"propagate_uplink_status"` + } + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + + p.PropagateUplinkStatusPtr = tmp.PropagateUplinkStatusPtr + return nil } type NetworkClient interface { @@ -189,6 +225,7 @@ func (c networkClient) ListPort(ctx context.Context, opts ports.ListOptsBuilder) } return resources, nil } + pager := ports.List(c.serviceClient, opts) return func(yield func(*PortExt, error) bool) { _ = pager.EachPage(ctx, yieldPage(extractPortExt, yield)) diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index 4bdf68bdb..e0935b42c 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -25,21 +25,22 @@ import ( // PortResourceSpecApplyConfiguration represents a declarative configuration of the PortResourceSpec type for use // with apply. type PortResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` - NetworkRef *apiv1alpha1.KubernetesNameRef `json:"networkRef,omitempty"` - Tags []apiv1alpha1.NeutronTag `json:"tags,omitempty"` - AllowedAddressPairs []AllowedAddressPairApplyConfiguration `json:"allowedAddressPairs,omitempty"` - Addresses []AddressApplyConfiguration `json:"addresses,omitempty"` - AdminStateUp *bool `json:"adminStateUp,omitempty"` - SecurityGroupRefs []apiv1alpha1.KubernetesNameRef `json:"securityGroupRefs,omitempty"` - VNICType *string `json:"vnicType,omitempty"` - PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` - ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` - MACAddress *string `json:"macAddress,omitempty"` - HostID *HostIDApplyConfiguration `json:"hostID,omitempty"` - TrustedVIF *bool `json:"trustedVIF,omitempty"` - ValueSpecs []PortValueSpecApplyConfiguration `json:"valueSpecs,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` + NetworkRef *apiv1alpha1.KubernetesNameRef `json:"networkRef,omitempty"` + Tags []apiv1alpha1.NeutronTag `json:"tags,omitempty"` + AllowedAddressPairs []AllowedAddressPairApplyConfiguration `json:"allowedAddressPairs,omitempty"` + Addresses []AddressApplyConfiguration `json:"addresses,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + SecurityGroupRefs []apiv1alpha1.KubernetesNameRef `json:"securityGroupRefs,omitempty"` + VNICType *string `json:"vnicType,omitempty"` + PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + MACAddress *string `json:"macAddress,omitempty"` + HostID *HostIDApplyConfiguration `json:"hostID,omitempty"` + TrustedVIF *bool `json:"trustedVIF,omitempty"` + ValueSpecs []PortValueSpecApplyConfiguration `json:"valueSpecs,omitempty"` + PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -186,3 +187,11 @@ func (b *PortResourceSpecApplyConfiguration) WithValueSpecs(values ...*PortValue } return b } + +// WithPropagateUplinkStatus sets the PropagateUplinkStatus field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PropagateUplinkStatus field is set to the value of the last call. +func (b *PortResourceSpecApplyConfiguration) WithPropagateUplinkStatus(value bool) *PortResourceSpecApplyConfiguration { + b.PropagateUplinkStatus = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index abfc36fe8..6ce4d211f 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1804,6 +1804,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: projectRef type: scalar: string + - name: propagateUplinkStatus + type: + scalar: boolean - name: securityGroupRefs type: list: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 1ccbb7352..c16b334ae 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2785,6 +2785,7 @@ _Appears in:_ | `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: \{\}
| | `trustedVIF` _boolean_ | trustedVIF indicates whether the VF for the port will become
trusted by physical function to perform some privileged
operations. Only admin users can create ports with this field. | | Optional: \{\}
| | `valueSpecs` _[PortValueSpec](#portvaluespec) array_ | valueSpecs are extra parameters to include in the API request
with OpenStack. This is an extension point for the API, so what
they do and if they are supported, depends on the specific
OpenStack implementation. This was meant to work similar to the
property on Heat port resource. Since this depends on the
underlying implementation, we can't predict its fields, and
therefore, we don't know how to reconcile them in advance. Use
this field wisely and be aware of the expected behavior. | | MaxItems: 128
Optional: \{\}
| +| `propagateUplinkStatus` _boolean_ | propagateUplinkStatus represents the uplink status propagation of
the port.
The field is now immutable due to a limitation on
Dalmatian (2024.2) release, we should address this later.
https://github.com/k-orc/openstack-resource-controller/pull/641#discussion_r2694783787 | | Optional: \{\}
| #### PortResourceStatus From 367dd141a9a2a3e654f9c756dc785bde61c02752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 10:18:05 +0200 Subject: [PATCH 213/237] RoleAssignment: remove resource ID from API types and generated code OpenStack role assignments are relationship resources without an OpenStack-assigned ID. Add NoResourceID support to the resource generator templates and set it for RoleAssignment, removing import.id, status.id, and the ID print column from the CRD. --- api/v1alpha1/zz_generated.deepcopy.go | 10 --- .../zz_generated.roleassignment-resource.go | 14 ---- cmd/models-schema/zz_generated.openapi.go | 14 ---- cmd/resource-generator/data/adapter.template | 8 +++ cmd/resource-generator/data/api.template | 8 +++ cmd/resource-generator/main.go | 9 ++- ...openstack.k-orc.cloud_roleassignments.yaml | 17 ----- internal/controllers/roleassignment/status.go | 67 ------------------- .../roleassignment/zz_generated.adapter.go | 7 +- .../api/v1alpha1/roleassignmentimport.go | 9 --- .../api/v1alpha1/roleassignmentstatus.go | 9 --- .../applyconfiguration/internal/internal.go | 6 -- test/apivalidations/roleassignment_test.go | 4 +- website/docs/crd-reference.md | 5 +- 14 files changed, 28 insertions(+), 159 deletions(-) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a8f37b4e5..23b45f76e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3852,11 +3852,6 @@ func (in *RoleAssignmentFilter) DeepCopy() *RoleAssignmentFilter { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RoleAssignmentImport) DeepCopyInto(out *RoleAssignmentImport) { *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(RoleAssignmentFilter) @@ -3997,11 +3992,6 @@ func (in *RoleAssignmentStatus) DeepCopyInto(out *RoleAssignmentStatus) { (*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(RoleAssignmentResourceStatus) diff --git a/api/v1alpha1/zz_generated.roleassignment-resource.go b/api/v1alpha1/zz_generated.roleassignment-resource.go index 03d1eee39..0453da8a4 100644 --- a/api/v1alpha1/zz_generated.roleassignment-resource.go +++ b/api/v1alpha1/zz_generated.roleassignment-resource.go @@ -24,15 +24,7 @@ import ( // RoleAssignmentImport specifies an existing resource which will be imported instead of // creating a new one // +kubebuilder:validation:MinProperties:=1 -// +kubebuilder:validation:MaxProperties:=1 type RoleAssignmentImport 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 @@ -104,11 +96,6 @@ type RoleAssignmentStatus struct { // +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 *RoleAssignmentResourceStatus `json:"resource,omitempty"` @@ -124,7 +111,6 @@ func (i *RoleAssignment) GetConditions() []metav1.Condition { // +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" diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index b41b708d5..c9bee77d9 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -7304,13 +7304,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentImport(r Description: "RoleAssignmentImport 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.", @@ -7552,13 +7545,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentStatus(r }, }, }, - "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.", diff --git a/cmd/resource-generator/data/adapter.template b/cmd/resource-generator/data/adapter.template index 7bec457ee..dca45ba90 100644 --- a/cmd/resource-generator/data/adapter.template +++ b/cmd/resource-generator/data/adapter.template @@ -55,7 +55,11 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { } func (f adapterT) GetStatusID() *string { +{{- if .NoResourceID }} + return nil +{{- else }} return f.Status.ID +{{- end }} } func (f adapterT) GetResourceSpec() *resourceSpecT { @@ -63,10 +67,14 @@ func (f adapterT) GetResourceSpec() *resourceSpecT { } func (f adapterT) GetImportID() *string { +{{- if .NoResourceID }} + return nil +{{- else }} if f.Spec.Import == nil { return nil } return f.Spec.Import.ID +{{- end }} } func (f adapterT) GetImportFilter() *filterT { diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index 9abb00d7a..04e5e0bed 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -23,8 +23,11 @@ import ( // {{ .Name }}Import specifies an existing resource which will be imported instead of // creating a new one // +kubebuilder:validation:MinProperties:=1 +{{- if not .NoResourceID }} // +kubebuilder:validation:MaxProperties:=1 +{{- end }} type {{ .Name }}Import struct { +{{- if not .NoResourceID }} {{- if .UsesNameAsID }} // id contains the name of an existing resource. Note: This resource uses // the resource name as the unique identifier, not a UUID. @@ -41,6 +44,7 @@ type {{ .Name }}Import struct { // +kubebuilder:validation:MaxLength:=36 // +optional ID *string `json:"id,omitempty"` //nolint:kubeapilinter +{{- end }} {{- end }} // filter contains a resource query which is expected to return a single @@ -118,11 +122,13 @@ type {{ .Name }}Status struct { // +listMapKey=type // +optional Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +{{- if not .NoResourceID }} // id is the unique identifier of the OpenStack resource. // +kubebuilder:validation:MaxLength:=1024 // +optional ID *string `json:"id,omitempty"` +{{- end }} // resource contains the observed state of the OpenStack resource. // +optional @@ -143,7 +149,9 @@ func (i *{{ .Name }}) GetConditions() []metav1.Condition { // +kubebuilder:object:root=true // +kubebuilder:resource:categories=openstack // +kubebuilder:subresource:status +{{- if not .NoResourceID }} // +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID" +{{- end }} // +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource" {{- range .AdditionalPrintColumns }} // +kubebuilder:printcolumn:name="{{ .Name }}",type="{{ .Type }}",JSONPath="{{ .JSONPath }}",description="{{ .Description }}" diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 7e1bd4147..bc32a1244 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -67,6 +67,10 @@ type templateFields struct { // When true, the UUID validation will be omitted from the Import.ID field. // Default is false (uses UUID). UsesNameAsID bool + // NoResourceID indicates this is a relationship resource without an + // OpenStack-assigned ID. When true, the generator omits import.id, + // status.ID, and the ID print column from the generated API types. + NoResourceID bool } var resources []templateFields = []templateFields{ @@ -125,8 +129,9 @@ var resources []templateFields = []templateFields{ Name: "Role", }, { - Name: "RoleAssignment", - IsNotNamed: true, + Name: "RoleAssignment", + IsNotNamed: true, + NoResourceID: true, }, { Name: "Router", diff --git a/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml index 2c3123c35..3668ad03e 100644 --- a/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml @@ -17,10 +17,6 @@ spec: 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 @@ -80,7 +76,6 @@ spec: description: |- import refers to an existing OpenStack resource which will be imported instead of creating a new one. - maxProperties: 1 minProperties: 1 properties: filter: @@ -118,14 +113,6 @@ spec: minLength: 1 type: string type: object - id: - description: |- - id contains the unique identifier of an existing OpenStack resource. Note - that when specifying an import by ID, the resource MUST already exist. - The ORC object will enter an error state if the resource does not exist. - format: uuid - maxLength: 36 - type: string type: object managedOptions: description: managedOptions specifies options which may be applied @@ -305,10 +292,6 @@ spec: 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. diff --git a/internal/controllers/roleassignment/status.go b/internal/controllers/roleassignment/status.go index c7587b320..c69b72bab 100644 --- a/internal/controllers/roleassignment/status.go +++ b/internal/controllers/roleassignment/status.go @@ -15,70 +15,3 @@ limitations under the License. */ package roleassignment - -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 roleassignmentStatusWriter struct{} - -type objectApplyT = orcapplyconfigv1alpha1.RoleAssignmentApplyConfiguration -type statusApplyT = orcapplyconfigv1alpha1.RoleAssignmentStatusApplyConfiguration - -var _ interfaces.ResourceStatusWriter[*orcv1alpha1.RoleAssignment, *osResourceT, *objectApplyT, *statusApplyT] = roleassignmentStatusWriter{} - -func (roleassignmentStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { - return orcapplyconfigv1alpha1.RoleAssignment(name, namespace) -} - -// ResourceAvailableStatus returns the availability status of the role assignment. -// Role assignments don't have Status.ID, so we just check if osResource exists. -func (roleassignmentStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.RoleAssignment, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { - if osResource == nil { - // Check if we have any status IDs set (indicates we may have created it but can't find it) - if orcObject.Status.Resource != nil && - (orcObject.Status.Resource.RoleID != "" || - orcObject.Status.Resource.UserID != "" || - orcObject.Status.Resource.GroupID != "" || - orcObject.Status.Resource.ProjectID != "" || - orcObject.Status.Resource.DomainID != "") { - return metav1.ConditionUnknown, nil - } - return metav1.ConditionFalse, nil - } - return metav1.ConditionTrue, nil -} - -// ApplyResourceStatus extracts the role assignment details and applies them to status. -func (roleassignmentStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { - resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus() - - // Extract role ID - if osResource.Role.ID != "" { - resourceStatus.WithRoleID(osResource.Role.ID) - } - - // Extract actor ID (user XOR group) - if osResource.User.ID != "" { - resourceStatus.WithUserID(osResource.User.ID) - } - if osResource.Group.ID != "" { - resourceStatus.WithGroupID(osResource.Group.ID) - } - - // Extract scope ID (project XOR domain) - if osResource.Scope.Project.ID != "" { - resourceStatus.WithProjectID(osResource.Scope.Project.ID) - } - if osResource.Scope.Domain.ID != "" { - resourceStatus.WithDomainID(osResource.Scope.Domain.ID) - } - - statusApply.WithResource(resourceStatus) -} diff --git a/internal/controllers/roleassignment/zz_generated.adapter.go b/internal/controllers/roleassignment/zz_generated.adapter.go index 53f478a76..9941f472b 100644 --- a/internal/controllers/roleassignment/zz_generated.adapter.go +++ b/internal/controllers/roleassignment/zz_generated.adapter.go @@ -56,7 +56,7 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { } func (f adapterT) GetStatusID() *string { - return f.Status.ID + return nil } func (f adapterT) GetResourceSpec() *resourceSpecT { @@ -64,10 +64,7 @@ func (f adapterT) GetResourceSpec() *resourceSpecT { } func (f adapterT) GetImportID() *string { - if f.Spec.Import == nil { - return nil - } - return f.Spec.Import.ID + return nil } func (f adapterT) GetImportFilter() *filterT { diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go index 364d27bcf..172fa19d9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go @@ -21,7 +21,6 @@ package v1alpha1 // RoleAssignmentImportApplyConfiguration represents a declarative configuration of the RoleAssignmentImport type for use // with apply. type RoleAssignmentImportApplyConfiguration struct { - ID *string `json:"id,omitempty"` Filter *RoleAssignmentFilterApplyConfiguration `json:"filter,omitempty"` } @@ -31,14 +30,6 @@ func RoleAssignmentImport() *RoleAssignmentImportApplyConfiguration { return &RoleAssignmentImportApplyConfiguration{} } -// 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 *RoleAssignmentImportApplyConfiguration) WithID(value string) *RoleAssignmentImportApplyConfiguration { - 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. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go index 1bb4a0d56..bf5edaa54 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go @@ -26,7 +26,6 @@ import ( // with apply. type RoleAssignmentStatusApplyConfiguration struct { Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` Resource *RoleAssignmentResourceStatusApplyConfiguration `json:"resource,omitempty"` } @@ -49,14 +48,6 @@ func (b *RoleAssignmentStatusApplyConfiguration) WithConditions(values ...*v1.Co 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 *RoleAssignmentStatusApplyConfiguration) WithID(value string) *RoleAssignmentStatusApplyConfiguration { - 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. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 5f557c2ab..2e7a00ec6 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2135,9 +2135,6 @@ var schemaYAML = typed.YAMLObject(`types: - name: filter type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentFilter - - name: id - type: - scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceSpec map: fields: @@ -2204,9 +2201,6 @@ var schemaYAML = typed.YAMLObject(`types: elementRelationship: associative keys: - type - - name: id - type: - scalar: string - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceStatus diff --git a/test/apivalidations/roleassignment_test.go b/test/apivalidations/roleassignment_test.go index 58a3b4b59..a48ba150a 100644 --- a/test/apivalidations/roleassignment_test.go +++ b/test/apivalidations/roleassignment_test.go @@ -30,7 +30,6 @@ import ( const ( roleassignmentName = "roleassignment" - roleassignmentID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" ) func roleassignmentStub(namespace *corev1.Namespace) *orcv1alpha1.RoleAssignment { @@ -54,7 +53,8 @@ func baseRoleAssignmentPatch(obj client.Object) *applyconfigv1alpha1.RoleAssignm } func testRoleAssignmentImport() *applyconfigv1alpha1.RoleAssignmentImportApplyConfiguration { - return applyconfigv1alpha1.RoleAssignmentImport().WithID(roleassignmentID) + return applyconfigv1alpha1.RoleAssignmentImport(). + WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithRoleRef("admin")) } var _ = Describe("ORC RoleAssignment API validations", func() { diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index fa2bc8073..9ff67d50d 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -3122,7 +3122,6 @@ RoleAssignmentImport specifies an existing resource which will be imported inste creating a new one _Validation:_ -- MaxProperties: 1 - MinProperties: 1 _Appears in:_ @@ -3130,7 +3129,6 @@ _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
Optional: \{\}
| | `filter` _[RoleAssignmentFilter](#roleassignmentfilter)_ | 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: \{\}
| @@ -3192,7 +3190,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `import` _[RoleAssignmentImport](#roleassignmentimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `import` _[RoleAssignmentImport](#roleassignmentimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MinProperties: 1
Optional: \{\}
| | `resource` _[RoleAssignmentResourceSpec](#roleassignmentresourcespec)_ | 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: \{\}
| @@ -3213,7 +3211,6 @@ _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
Optional: \{\}
| -| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[RoleAssignmentResourceStatus](#roleassignmentresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| From 0e9813de25c001ac57e11ce198f35c3cadbf2162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 10:58:14 +0200 Subject: [PATCH 214/237] roleassignment: implement import-by-filter path The roleassignment reconciler was missing the import path entirely. Add it to reconcileNormal, between the existing status check and creation phases. The implementation mirrors the generic reconciler's GetOrCreateOSResource import-by-filter logic: - Resolves filter dependencies via ListOSResourcesForImport - Validates at most one result (terminal error on multiple matches) - Polls with 15s interval when no match is found yet Add KUTTL E2E tests. --- .../controllers/roleassignment/reconciler.go | 59 +++++++++++++- .../00-assert.yaml | 43 ++++++++++ .../00-import-resource.yaml | 56 +++++++++++++ .../00-secret.yaml | 6 ++ .../01-assert.yaml | 30 +++++++ .../01-create-trap-resource.yaml | 28 +++++++ .../02-assert.yaml | 39 ++++++++++ .../02-create-resource.yaml | 29 +++++++ .../03-assert.yaml | 6 ++ .../03-delete-import-dependencies.yaml | 7 ++ .../04-assert.yaml | 6 ++ .../04-delete-resource.yaml | 7 ++ .../README.md | 32 ++++++++ .../00-assert.yaml | 78 +++++++++++++++++++ .../00-create-resources.yaml | 76 ++++++++++++++++++ .../00-secret.yaml | 6 ++ .../01-assert.yaml | 15 ++++ .../01-import-resource.yaml | 14 ++++ .../roleassignment-import-error/README.md | 17 ++++ .../roleassignment-import/00-assert.yaml | 54 +++++++++++++ .../00-import-resource.yaml | 51 ++++++++++++ .../roleassignment-import/00-secret.yaml | 6 ++ .../roleassignment-import/01-assert.yaml | 28 +++++++ .../01-create-trap-resource.yaml | 29 +++++++ .../roleassignment-import/02-assert.yaml | 39 ++++++++++ .../02-create-resource.yaml | 15 ++++ .../tests/roleassignment-import/README.md | 23 ++++++ 27 files changed, 798 insertions(+), 1 deletion(-) create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/README.md diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go index 8084a6efd..42dae316b 100644 --- a/internal/controllers/roleassignment/reconciler.go +++ b/internal/controllers/roleassignment/reconciler.go @@ -19,6 +19,7 @@ package roleassignment import ( "context" "fmt" + "iter" "time" corev1 "k8s.io/api/core/v1" @@ -42,6 +43,11 @@ import ( orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" ) +const ( + // The time to wait before reconciling again when we are waiting for some change in OpenStack + externalUpdatePollingPeriod = 15 * time.Second +) + // roleassignmentReconciler reconciles RoleAssignment objects. // Unlike other ORC resources, role assignments are relationships (not resources with IDs), // so this uses a custom reconciler instead of the generic framework. @@ -165,7 +171,32 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec } } - // Phase 5: Fetch dependencies and create role assignment + // Phase 5: Import by filter + if importSpec := orcObject.Spec.Import; importSpec != nil { + if filter := importSpec.Filter; filter != nil { + resourceIter, importRS := actuator.ListOSResourcesForImport(ctx, orcObject, *filter) + if needsReschedule, _ := importRS.NeedsReschedule(); needsReschedule { + return importRS.WithReconcileStatus(reconcileStatus) + } + + var err error + osResource, err = atMostOne(resourceIter, + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "found more than one matching OpenStack resource during import")) + if err != nil { + return progress.WrapError(err) + } + + if osResource == nil { + return progress.WaitingOnOpenStack(progress.WaitingOnCreation, externalUpdatePollingPeriod) + } + + log.V(logging.Info).Info("Imported role assignment") + return reconcileStatus + } + } + + // Phase 6: Fetch dependencies and create role assignment osResource, createRS := actuator.CreateResource(ctx, orcObject) if needsReschedule, err := createRS.NeedsReschedule(); needsReschedule { if err == nil { @@ -182,6 +213,32 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec return reconcileStatus } +// atMostOne returns the first element from the iterator, or nil if it's empty. +// It returns multipleErr if the iterator yields more than one element. +func atMostOne(resourceIter iter.Seq2[*osResourceT, error], multipleErr error) (*osResourceT, error) { + next, stop := iter.Pull2(resourceIter) + defer stop() + + // Try to fetch the first result + osResource, err, ok := next() + if err != nil { + return nil, err + } else if !ok { + // No first result + return nil, nil + } + + // Check that there are no other results + _, err, ok = next() + if err != nil { + return nil, err + } else if ok { + return nil, multipleErr + } + + return osResource, nil +} + // reconcileDelete handles deletion of the RoleAssignment: // 1. Check finalizer // 2. Fetch the role assignment (using Status.Resource components) diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml new file mode 100644 index 000000000..c90c27041 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dep-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dep-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep +status: + conditions: + - type: Available + message: |- + Waiting for Role/roleassignment-import-dep-role to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Role/roleassignment-import-dep-role to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..f795c68ae --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml @@ -0,0 +1,56 @@ +--- +# Unmanaged Role that imports by name. No matching OpenStack role exists yet, +# so this will stay in Progressing state. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dep-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dep-ext-role +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dep-user +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-dep-user +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dep-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-dep-project +--- +# Import RoleAssignment referencing the unmanaged Role. Since the Role is not +# yet available, this should wait on the dependency. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + roleRef: roleassignment-import-dep-role + userRef: roleassignment-import-dep-user + projectRef: roleassignment-import-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml new file mode 100644 index 000000000..105272ace --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep-trap +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep +status: + conditions: + - type: Available + message: |- + Waiting for Role/roleassignment-import-dep-role to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Role/roleassignment-import-dep-role to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..7b4db41fa --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dep-trap-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-dep-trap-role +--- +# This role assignment uses a different role but the same user and project. +# It should not be picked by the import filter. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep-trap +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-dep-trap-role + userRef: roleassignment-import-dep-user + projectRef: roleassignment-import-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml new file mode 100644 index 000000000..4e44d0fa3 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml @@ -0,0 +1,39 @@ +--- +# Verify the imported role assignment matches the created one +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dep + ref: importedRA + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dep-external + ref: externalRA + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-import-dep-role + ref: role +assertAll: + # Import should have same component IDs as external + - celExpr: "importedRA.status.resource.roleID == externalRA.status.resource.roleID" + - celExpr: "importedRA.status.resource.userID == externalRA.status.resource.userID" + - celExpr: "importedRA.status.resource.projectID == externalRA.status.resource.projectID" + # The roleID should match the unmanaged Role's imported status.id + - celExpr: "importedRA.status.resource.roleID == role.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep +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/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..1165c7af5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml @@ -0,0 +1,29 @@ +--- +# Create the managed Role that satisfies the unmanaged Role's import filter. +# The unmanaged Role imports by filter name: roleassignment-import-dep-ext-role +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dep-ext-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-dep-ext-role +--- +# Create the role assignment matching the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dep-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-dep-ext-role + userRef: roleassignment-import-dep-user + projectRef: roleassignment-import-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml new file mode 100644 index 000000000..396d7efe5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-import-dep-role --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..e1b01ffab --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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 role.openstack.k-orc.cloud roleassignment-import-dep-role + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml new file mode 100644 index 000000000..9ac45ea07 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get roleassignment roleassignment-import-dep --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..4c0c62425 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dep diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md new file mode 100644 index 000000000..b804056ba --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md @@ -0,0 +1,32 @@ +# Check dependency handling for imported RoleAssignment + +## Step 00 + +Create an unmanaged Role importing by filter (name that doesn't exist yet), +managed User and Project dependencies, and an unmanaged RoleAssignment +importing by filter with roleRef pointing to the unmanaged Role. +Verify the RoleAssignment is waiting for the Role dependency to be ready. + +## Step 01 + +Create a trap RoleAssignment with a different role but the same user and +project, and verify that it is not being imported. + +## Step 02 + +Create a managed Role matching the unmanaged Role's import filter and a +managed RoleAssignment matching the import filter. Verify the imported +RoleAssignment is available with correct component IDs. + +## Step 03 + +Delete the import dependency (the unmanaged Role) and verify ORC does not +prevent deletion. Import dependencies should not have deletion guards. + +## Step 04 + +Delete the imported RoleAssignment and verify it's gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml new file mode 100644 index 000000000..44cbe53fb --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml @@ -0,0 +1,78 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-err-role-1 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-err-role-2 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-err-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-err-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-err-1 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-err-2 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml new file mode 100644 index 000000000..f5f499b58 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-err-role-1 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-err-role-1 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-err-role-2 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-err-role-2 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-err-user +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-err-user +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-err-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-err-project +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-err-1 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-err-role-1 + userRef: roleassignment-import-err-user + projectRef: roleassignment-import-err-project +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-err-2 +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-err-role-2 + userRef: roleassignment-import-err-user + projectRef: roleassignment-import-err-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-import-error/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml new file mode 100644 index 000000000..1f7e3a893 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml new file mode 100644 index 000000000..030399b68 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + userRef: roleassignment-import-err-user + projectRef: roleassignment-import-err-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md new file mode 100644 index 000000000..ffe332e6d --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md @@ -0,0 +1,17 @@ +# Import RoleAssignment Error + +## Step 00 + +Create dependencies (User, two Roles, a Project) as managed resources, and +two managed RoleAssignments assigning each role to the same user on the same +project. + +## Step 01 + +Import an unmanaged RoleAssignment using a filter that specifies only userRef +and projectRef. Both role assignments match the filter, causing a terminal +error because more than one matching resource was found. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml new file mode 100644 index 000000000..175a5e837 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml new file mode 100644 index 000000000..e3e0c8df5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-role +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-user +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-user +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-project +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + roleRef: roleassignment-import-role + userRef: roleassignment-import-user + projectRef: roleassignment-import-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-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/roleassignment/tests/roleassignment-import/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml new file mode 100644 index 000000000..02fb6038b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-trap +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..6bc4c7745 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml @@ -0,0 +1,29 @@ +--- +# Create a different role to use in the trap +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-trap-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-import-trap-role +--- +# This role assignment uses a different role but the same user and project. +# It should not be picked by the import filter which specifies a different roleRef. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-trap +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-trap-role + userRef: roleassignment-import-user + projectRef: roleassignment-import-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml new file mode 100644 index 000000000..9552fa89e --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml @@ -0,0 +1,39 @@ +--- +# Verify the imported role assignment matches the created one +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import + ref: importedRA + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-external + ref: externalRA + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-trap + ref: trapRA +assertAll: + # Import should have same component IDs as external + - celExpr: "importedRA.status.resource.roleID == externalRA.status.resource.roleID" + - celExpr: "importedRA.status.resource.userID == externalRA.status.resource.userID" + - celExpr: "importedRA.status.resource.projectID == externalRA.status.resource.projectID" + # Import should not have picked the trap (different role ID) + - celExpr: "importedRA.status.resource.roleID != trapRA.status.resource.roleID" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-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 diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml new file mode 100644 index 000000000..e01728436 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml @@ -0,0 +1,15 @@ +--- +# Create the role assignment matching the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-external +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-role + userRef: roleassignment-import-user + projectRef: roleassignment-import-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/README.md b/internal/controllers/roleassignment/tests/roleassignment-import/README.md new file mode 100644 index 000000000..ac505ff33 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/README.md @@ -0,0 +1,23 @@ +# Import RoleAssignment + +## Step 00 + +Create dependencies (Role, User, Project) as managed resources, and an +unmanaged RoleAssignment importing by filter that references all three. +Verify that the import RoleAssignment is waiting for the external resource +to be created in OpenStack. + +## Step 01 + +Create a trap RoleAssignment using a different role but the same user and +project, and verify that it is not being imported by the filter. + +## Step 02 + +Create a managed RoleAssignment matching the import filter and verify that +the imported RoleAssignment picks it up with the correct component IDs. +Also verify that the imported RoleAssignment didn't pick the trap. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import From bd0a13a114d265192a4a74e1ec3f9c1cec9f42ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 11:37:19 +0200 Subject: [PATCH 215/237] roleassignment: use ResourceStatusWriter from generic framework Split ORCStatusApplyConfig into a base interface (WithConditions only) and ORCStatusApplyConfigWithID (adds WithID) so that resources without an OpenStack-assigned ID can use ResourceStatusWriter and status.UpdateStatus without requiring a WithID method on their status apply configuration. The generic Controller and SetStatusID use ORCStatusApplyConfigWithID, so existing controllers are unaffected. RoleAssignment's custom reconciler now delegates status writing to status.UpdateStatus via a ResourceStatusWriter implementation, replacing the 40-line inline updateStatus method and aligning with the pattern used by all other controllers. --- .../controllers/generic/interfaces/status.go | 10 ++- .../generic/reconciler/controller.go | 4 +- internal/controllers/generic/status/status.go | 2 +- .../controllers/roleassignment/reconciler.go | 80 ++----------------- internal/controllers/roleassignment/status.go | 66 +++++++++++++++ .../development/godoc/generic-interfaces.md | 18 ++++- 6 files changed, 101 insertions(+), 79 deletions(-) diff --git a/internal/controllers/generic/interfaces/status.go b/internal/controllers/generic/interfaces/status.go index b577a364f..6f7088760 100644 --- a/internal/controllers/generic/interfaces/status.go +++ b/internal/controllers/generic/interfaces/status.go @@ -35,9 +35,17 @@ type ORCApplyConfig[objectApplyPT any, statusApplyPT ORCStatusApplyConfig[status } // ORCStatusApplyConfig is an interface implemented by the status of any apply -// configuration for an ORC API object. It has Conditions and an ID field. +// configuration for an ORC API object. type ORCStatusApplyConfig[statusApplyPT any] interface { WithConditions(...*applyconfigv1.ConditionApplyConfiguration) statusApplyPT +} + +// ORCStatusApplyConfigWithID extends ORCStatusApplyConfig with an ID field. +// This is required by resources that have an OpenStack-assigned ID stored in +// status.id. Resources without an ID (e.g. relationship resources like +// RoleAssignment) use only ORCStatusApplyConfig. +type ORCStatusApplyConfigWithID[statusApplyPT any] interface { + ORCStatusApplyConfig[statusApplyPT] WithID(id string) statusApplyPT } diff --git a/internal/controllers/generic/reconciler/controller.go b/internal/controllers/generic/reconciler/controller.go index 7571519cd..d74a6f18d 100644 --- a/internal/controllers/generic/reconciler/controller.go +++ b/internal/controllers/generic/reconciler/controller.go @@ -51,7 +51,7 @@ func NewController[ objectApplyPT interfaces.ORCApplyConfig[objectApplyPT, statusApplyPT], statusApplyPT interface { *statusApplyT - interfaces.ORCStatusApplyConfig[statusApplyPT] + interfaces.ORCStatusApplyConfigWithID[statusApplyPT] }, statusApplyT any, osResourceT any, ]( @@ -80,7 +80,7 @@ type Controller[ objectApplyPT interfaces.ORCApplyConfig[objectApplyPT, statusApplyPT], statusApplyPT interface { *statusApplyT - interfaces.ORCStatusApplyConfig[statusApplyPT] + interfaces.ORCStatusApplyConfigWithID[statusApplyPT] }, statusApplyT any, osResourceT any, diff --git a/internal/controllers/generic/status/status.go b/internal/controllers/generic/status/status.go index 4776d16ae..990eb1ed0 100644 --- a/internal/controllers/generic/status/status.go +++ b/internal/controllers/generic/status/status.go @@ -41,7 +41,7 @@ func SetStatusID[ objectApplyPT interfaces.ORCApplyConfig[objectApplyPT, statusApplyPT], statusApplyPT interface { *statusApplyT - interfaces.ORCStatusApplyConfig[statusApplyPT] + interfaces.ORCStatusApplyConfigWithID[statusApplyPT] }, statusApplyT any, osResourcePT any, diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go index 42dae316b..818aaff8b 100644 --- a/internal/controllers/roleassignment/reconciler.go +++ b/internal/controllers/roleassignment/reconciler.go @@ -26,7 +26,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -36,11 +35,9 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/status" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/scope" - "github.com/k-orc/openstack-resource-controller/v2/internal/util/applyconfigs" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" "github.com/k-orc/openstack-resource-controller/v2/internal/util/finalizers" orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" - orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" ) const ( @@ -54,8 +51,14 @@ const ( type roleassignmentReconciler struct { client client.Client scopeFactory scope.Factory + + statusWriter roleassignmentStatusWriter } +func (r *roleassignmentReconciler) GetName() string { return controllerName } +func (r *roleassignmentReconciler) GetK8sClient() client.Client { return r.client } +func (r *roleassignmentReconciler) GetScopeFactory() scope.Factory { return r.scopeFactory } + // Reconcile is the main entry point for reconciliation. // It fetches the RoleAssignment object and routes to either reconcileNormal or reconcileDelete. func (r *roleassignmentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -118,7 +121,7 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec // Ensure we always update status at the end defer func() { reconcileStatus = reconcileStatus.WithReconcileStatus( - r.updateStatus(ctx, orcObject, osResource, reconcileStatus)) + status.UpdateStatus(ctx, r, r.statusWriter, orcObject, osResource, reconcileStatus)) }() // Phase 3: Add finalizer if not present @@ -256,7 +259,7 @@ func (r *roleassignmentReconciler) reconcileDelete(ctx context.Context, orcObjec defer func() { if !deleted { reconcileStatus = reconcileStatus.WithReconcileStatus( - r.updateStatus(ctx, orcObject, osResource, reconcileStatus)) + status.UpdateStatus(ctx, r, r.statusWriter, orcObject, osResource, reconcileStatus)) } }() @@ -374,70 +377,3 @@ func (r *roleassignmentReconciler) newActuator(ctx context.Context, orcObject or k8sClient: r.client, }, nil } - -// updateStatus writes the observed state back to the Kubernetes object. -// This sets Status.Resource fields and conditions (Available, Progressing). -// Note: Status.ID is intentionally left nil for role assignments. -func (r *roleassignmentReconciler) updateStatus( - ctx context.Context, - orcObject orcObjectPT, - osResource *osResourceT, - reconcileStatus progress.ReconcileStatus, -) progress.ReconcileStatus { - log := ctrl.LoggerFrom(ctx) - now := metav1.NewTime(time.Now()) - - // Create apply configuration for status - statusApply := orcapplyconfigv1alpha1.RoleAssignmentStatus() - applyConfig := orcapplyconfigv1alpha1.RoleAssignment(orcObject.Name, orcObject.Namespace). - WithUID(orcObject.GetUID()). - WithStatus(statusApply) - - // Write resource status fields from osResource - if osResource != nil { - resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus() - - if osResource.Role.ID != "" { - resourceStatus.WithRoleID(osResource.Role.ID) - } - if osResource.User.ID != "" { - resourceStatus.WithUserID(osResource.User.ID) - } - if osResource.Group.ID != "" { - resourceStatus.WithGroupID(osResource.Group.ID) - } - if osResource.Scope.Project.ID != "" { - resourceStatus.WithProjectID(osResource.Scope.Project.ID) - } - if osResource.Scope.Domain.ID != "" { - resourceStatus.WithDomainID(osResource.Scope.Domain.ID) - } - - statusApply.WithResource(resourceStatus) - } - - // Determine Available status - availableStatus := metav1.ConditionFalse - if osResource != nil { - availableStatus = metav1.ConditionTrue - } else if orcObject.Status.Resource != nil && - (orcObject.Status.Resource.RoleID != "" || - orcObject.Status.Resource.UserID != "" || - orcObject.Status.Resource.GroupID != "" || - orcObject.Status.Resource.ProjectID != "" || - orcObject.Status.Resource.DomainID != "") { - availableStatus = metav1.ConditionUnknown - } - - // Set common conditions (Available and Progressing) - status.SetCommonConditions(orcObject, statusApply, availableStatus, reconcileStatus, now) - - // Patch status - ssaFieldOwner := orcstrings.GetSSAFieldOwnerWithTxn(controllerName, orcstrings.SSATransactionStatus) - if err := r.client.Status().Patch(ctx, orcObject, applyconfigs.Patch(types.ApplyPatchType, applyConfig), client.ForceOwnership, ssaFieldOwner); err != nil { - return progress.WrapError(fmt.Errorf("patching status: %w", err)) - } - - log.V(logging.Debug).Info("Updated status") - return nil -} diff --git a/internal/controllers/roleassignment/status.go b/internal/controllers/roleassignment/status.go index c69b72bab..2a938e9e7 100644 --- a/internal/controllers/roleassignment/status.go +++ b/internal/controllers/roleassignment/status.go @@ -15,3 +15,69 @@ limitations under the License. */ package roleassignment + +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 roleassignmentStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.RoleAssignmentApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.RoleAssignmentStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.RoleAssignment, *osResourceT, *objectApplyT, *statusApplyT] = roleassignmentStatusWriter{} + +func (roleassignmentStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.RoleAssignment(name, namespace) +} + +// ResourceAvailableStatus returns the availability status of the role assignment. +// Role assignments don't have Status.ID, so availability is based on osResource +// presence and status component fields. +func (roleassignmentStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.RoleAssignment, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { + if osResource != nil { + return metav1.ConditionTrue, nil + } + + // If we previously observed component IDs but can't fetch the resource now, + // report Unknown since we can't confirm availability. + if orcObject.Status.Resource != nil && + (orcObject.Status.Resource.RoleID != "" || + orcObject.Status.Resource.UserID != "" || + orcObject.Status.Resource.GroupID != "" || + orcObject.Status.Resource.ProjectID != "" || + orcObject.Status.Resource.DomainID != "") { + return metav1.ConditionUnknown, nil + } + + return metav1.ConditionFalse, nil +} + +// ApplyResourceStatus writes the role assignment component IDs to status. +func (roleassignmentStatusWriter) ApplyResourceStatus(_ logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus() + + if osResource.Role.ID != "" { + resourceStatus.WithRoleID(osResource.Role.ID) + } + if osResource.User.ID != "" { + resourceStatus.WithUserID(osResource.User.ID) + } + if osResource.Group.ID != "" { + resourceStatus.WithGroupID(osResource.Group.ID) + } + if osResource.Scope.Project.ID != "" { + resourceStatus.WithProjectID(osResource.Scope.Project.ID) + } + if osResource.Scope.Domain.ID != "" { + resourceStatus.WithDomainID(osResource.Scope.Domain.ID) + } + + statusApply.WithResource(resourceStatus) +} diff --git a/website/docs/development/godoc/generic-interfaces.md b/website/docs/development/godoc/generic-interfaces.md index 2d0021572..51536dfde 100644 --- a/website/docs/development/godoc/generic-interfaces.md +++ b/website/docs/development/godoc/generic-interfaces.md @@ -15,6 +15,7 @@ import "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/g - [type DeleteResourceActuator](<#DeleteResourceActuator>) - [type ORCApplyConfig](<#ORCApplyConfig>) - [type ORCStatusApplyConfig](<#ORCStatusApplyConfig>) +- [type ORCStatusApplyConfigWithID](<#ORCStatusApplyConfigWithID>) - [type ReconcileResourceActuator](<#ReconcileResourceActuator>) - [type ResourceController](<#ResourceController>) - [type ResourceHelperFactory](<#ResourceHelperFactory>) @@ -203,14 +204,25 @@ type ORCApplyConfig[objectApplyPT any, statusApplyPT ORCStatusApplyConfig[status ``` -## type [ORCStatusApplyConfig]() +## type [ORCStatusApplyConfig]() -ORCStatusApplyConfig is an interface implemented by the status of any apply configuration for an ORC API object. It has Conditions and an ID field. +ORCStatusApplyConfig is an interface implemented by the status of any apply configuration for an ORC API object. ```go type ORCStatusApplyConfig[statusApplyPT any] interface { WithConditions(...*applyconfigv1.ConditionApplyConfiguration) statusApplyPT +} +``` + + +## type [ORCStatusApplyConfigWithID]() + +ORCStatusApplyConfigWithID extends ORCStatusApplyConfig with an ID field. This is required by resources that have an OpenStack\-assigned ID stored in status.id. Resources without an ID \(e.g. relationship resources like RoleAssignment\) use only ORCStatusApplyConfig. + +```go +type ORCStatusApplyConfigWithID[statusApplyPT any] interface { WithID(id string) statusApplyPT + // contains filtered or unexported methods } ``` @@ -313,7 +325,7 @@ type ResourceReconciler[orcObjectPT, osResourceT any] func(ctx context.Context, ``` -## type [ResourceStatusWriter]() +## type [ResourceStatusWriter]() ResourceStatusWriter defines methods for writing an ORC object status From b8ef756509810c79e7237ae556900e9c0aa721d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 12:00:52 +0200 Subject: [PATCH 216/237] roleassignment: implement adoption flow in custom reconciler Add adoption logic to the roleassignment custom reconciler, matching the pattern used by the generic reconciler framework for ID-based resources. In reconcileNormal, between the import phase and the create phase: - Add an unmanaged policy guard that returns a terminal error - Call ListOSResourcesForAdoption to find and adopt an existing OpenStack resource before creating a new one In reconcileDelete, add orphan cleanup: - When Status.Resource was never populated, call ListOSResourcesForAdoption to find orphaned resources that were created but not recorded in status - Restructure actuator creation out of the Status.Resource block so it is shared by both the status-based fetch and the adoption-based orphan check Add unit tests for ListOSResourcesForAdoption covering nil resource spec, user+project scope, group+domain scope, empty OS results, and OS client error propagation. --- .../roleassignment/actuator_test.go | 331 +++++++++++++++++- .../controllers/roleassignment/reconciler.go | 76 +++- 2 files changed, 388 insertions(+), 19 deletions(-) diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go index c37d536c8..723c2f9c0 100644 --- a/internal/controllers/roleassignment/actuator_test.go +++ b/internal/controllers/roleassignment/actuator_test.go @@ -16,5 +16,332 @@ limitations under the License. package roleassignment -// NOTE: RoleAssignment is fully immutable, so there are no update functions to test. -// Tests for creation and deletion logic should be added as KUTTL E2E tests in the tests/ directory. +import ( + "context" + "errors" + "fmt" + "iter" + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" +) + +var ( + errNotImplemented = errors.New("not implemented") + errTest = errors.New("test error") +) + +const testNamespace = "test-ns" + +// mockRoleAssignmentClient is a simple mock that returns pre-configured assignments. +type mockRoleAssignmentClient struct { + assignments []roles.RoleAssignment +} + +var _ osclients.RoleAssignmentClient = mockRoleAssignmentClient{} + +func (m mockRoleAssignmentClient) ListRoleAssignments(_ context.Context, _ roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { + return func(yield func(*roles.RoleAssignment, error) bool) { + for i := range m.assignments { + if !yield(&m.assignments[i], nil) { + return + } + } + } +} + +func (m mockRoleAssignmentClient) AssignRole(_ context.Context, _ string, _ roles.AssignOpts) error { + return errNotImplemented +} + +func (m mockRoleAssignmentClient) UnassignRole(_ context.Context, _ string, _ roles.UnassignOpts) error { + return errNotImplemented +} + +// Test result type and check helpers + +type raResult struct { + assignment *roles.RoleAssignment + err error +} + +type checkFunc func([]raResult) error + +func checks(fns ...checkFunc) []checkFunc { return fns } + +func noError(results []raResult) error { + for _, result := range results { + if result.err != nil { + return fmt.Errorf("unexpected error: %w", result.err) + } + } + return nil +} + +func wantError(wantErr error) checkFunc { + return func(results []raResult) error { + for _, result := range results { + if result.err != nil && errors.Is(result.err, wantErr) { + return nil + } + } + return fmt.Errorf("expected error %v not found in results", wantErr) + } +} + +func findsN(wantN int) checkFunc { + return func(results []raResult) error { + found := len(results) + if found != wantN { + return fmt.Errorf("expected %d results, got %d", wantN, found) + } + return nil + } +} + +// availableCondition returns an Available=True condition for test objects. +func availableCondition() metav1.Condition { + return metav1.Condition{ + Type: orcv1alpha1.ConditionAvailable, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Reason: "Available", + } +} + +// newFakeK8sClient creates a fake k8s client with the given objects and ORC scheme. +func newFakeK8sClient(objects ...client.Object) client.Client { + scheme := runtime.NewScheme() + _ = orcv1alpha1.AddToScheme(scheme) + + return fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(objects...). + Build() +} + +// availableRole returns a Role object that is available with the given status ID. +func availableRole(name, statusID string) *orcv1alpha1.Role { + return &orcv1alpha1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Status: orcv1alpha1.RoleStatus{ + Conditions: []metav1.Condition{availableCondition()}, + ID: ptr.To(statusID), + }, + } +} + +// availableUser returns a User object that is available with the given status ID. +func availableUser(name, statusID string) *orcv1alpha1.User { + return &orcv1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Status: orcv1alpha1.UserStatus{ + Conditions: []metav1.Condition{availableCondition()}, + ID: ptr.To(statusID), + }, + } +} + +// availableGroup returns a Group object that is available with the given status ID. +func availableGroup(name, statusID string) *orcv1alpha1.Group { + return &orcv1alpha1.Group{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Status: orcv1alpha1.GroupStatus{ + Conditions: []metav1.Condition{availableCondition()}, + ID: ptr.To(statusID), + }, + } +} + +// availableProject returns a Project object that is available with the given status ID. +func availableProject(name, statusID string) *orcv1alpha1.Project { + return &orcv1alpha1.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Status: orcv1alpha1.ProjectStatus{ + Conditions: []metav1.Condition{availableCondition()}, + ID: ptr.To(statusID), + }, + } +} + +// availableDomain returns a Domain object that is available with the given status ID. +func availableDomain(name, statusID string) *orcv1alpha1.Domain { + return &orcv1alpha1.Domain{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Status: orcv1alpha1.DomainStatus{ + Conditions: []metav1.Condition{availableCondition()}, + ID: ptr.To(statusID), + }, + } +} + +func TestListOSResourcesForAdoption(t *testing.T) { + userProjectAssignment := roles.RoleAssignment{ + Role: roles.AssignedRole{ID: "role-id-1"}, + User: roles.User{ID: "user-id-1"}, + Scope: roles.Scope{Project: roles.Project{ID: "project-id-1"}}, + } + + groupDomainAssignment := roles.RoleAssignment{ + Role: roles.AssignedRole{ID: "role-id-2"}, + Group: roles.Group{ID: "group-id-2"}, + Scope: roles.Scope{Domain: roles.Domain{ID: "domain-id-2"}}, + } + + for _, tc := range [...]struct { + name string + orcObject *orcv1alpha1.RoleAssignment + k8sObjects []client.Object + osClient osclients.RoleAssignmentClient + wantAdopt bool + checks []checkFunc + }{ + { + name: "returns false when spec.resource is nil", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{}, + }, + osClient: mockRoleAssignmentClient{}, + wantAdopt: false, + }, + { + name: "user+project scope, all deps available, match found", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "test-role", + UserRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-user"), + ProjectRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-project"), + }, + }, + }, + k8sObjects: []client.Object{ + availableRole("test-role", "role-id-1"), + availableUser("test-user", "user-id-1"), + availableProject("test-project", "project-id-1"), + }, + osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{userProjectAssignment}}, + wantAdopt: true, + checks: checks(noError, findsN(1)), + }, + { + name: "group+domain scope, all deps available, match found", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "test-role", + GroupRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-group"), + DomainRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-domain"), + }, + }, + }, + k8sObjects: []client.Object{ + availableRole("test-role", "role-id-2"), + availableGroup("test-group", "group-id-2"), + availableDomain("test-domain", "domain-id-2"), + }, + osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{groupDomainAssignment}}, + wantAdopt: true, + checks: checks(noError, findsN(1)), + }, + { + name: "all deps available, no matches from OS", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "test-role", + UserRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-user"), + ProjectRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-project"), + }, + }, + }, + k8sObjects: []client.Object{ + availableRole("test-role", "role-id-1"), + availableUser("test-user", "user-id-1"), + availableProject("test-project", "project-id-1"), + }, + osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{}}, + wantAdopt: true, + checks: checks(noError, findsN(0)), + }, + { + name: "OS client returns error", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "test-role", + UserRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-user"), + ProjectRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-project"), + }, + }, + }, + k8sObjects: []client.Object{ + availableRole("test-role", "role-id-1"), + availableUser("test-user", "user-id-1"), + availableProject("test-project", "project-id-1"), + }, + osClient: osclients.NewRoleAssignmentErrorClient(errTest), + wantAdopt: true, + checks: checks(wantError(errTest)), + }, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + k8sClient := newFakeK8sClient(tc.k8sObjects...) + + actuator := roleassignmentActuator{ + osClient: tc.osClient, + k8sClient: k8sClient, + } + + resourceIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, tc.orcObject) + if canAdopt != tc.wantAdopt { + t.Fatalf("canAdopt = %v, want %v", canAdopt, tc.wantAdopt) + } + + if !canAdopt { + return + } + + var results []raResult + for assignment, err := range resourceIter { + results = append(results, raResult{assignment, err}) + } + + for _, check := range tc.checks { + if e := check(results); e != nil { + t.Error(e) + } + } + }) + } +} diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go index 818aaff8b..676e76a7f 100644 --- a/internal/controllers/roleassignment/reconciler.go +++ b/internal/controllers/roleassignment/reconciler.go @@ -199,7 +199,31 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec } } - // Phase 6: Fetch dependencies and create role assignment + // Phase 6: Adoption - check for existing resource before creating + if orcObject.Spec.ManagementPolicy == orcv1alpha1.ManagementPolicyUnmanaged { + // We never create an unmanaged resource + // API validation should have ensured that one of the above functions returned + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Not creating unmanaged resource")) + } + + resourceIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if canAdopt { + var err error + osResource, err = atMostOne(resourceIter, + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "found more than one matching OpenStack resource during adoption")) + if err != nil { + return progress.WrapError(err) + } + if osResource != nil { + log.V(logging.Info).Info("Adopted previously created resource") + return reconcileStatus + } + } + + // Phase 7: Fetch dependencies and create role assignment + log.V(logging.Info).Info("Creating resource") osResource, createRS := actuator.CreateResource(ctx, orcObject) if needsReschedule, err := createRS.NeedsReschedule(); needsReschedule { if err == nil { @@ -307,6 +331,15 @@ func (r *roleassignmentReconciler) reconcileDelete(ctx context.Context, orcObjec return removeFinalizer(reconcileStatus) } + // Create actuator for OpenStack operations + actuator, actuatorRS := r.newActuator(ctx, orcObject) + if needsReschedule, err := actuatorRS.NeedsReschedule(); needsReschedule { + if err == nil { + log.V(logging.Verbose).Info("Waiting on events before deletion") + } + return actuatorRS.WithReconcileStatus(reconcileStatus) + } + // Fetch the role assignment using Status.Resource components if orcObject.Status.Resource != nil { statusResource := orcObject.Status.Resource @@ -314,16 +347,8 @@ func (r *roleassignmentReconciler) reconcileDelete(ctx context.Context, orcObjec (statusResource.UserID != "" || statusResource.GroupID != "") && (statusResource.ProjectID != "" || statusResource.DomainID != "") { - // Create actuator for deletion - actuator, actuatorRS := r.newActuator(ctx, orcObject) - if needsReschedule, err := actuatorRS.NeedsReschedule(); needsReschedule { - if err == nil { - log.V(logging.Verbose).Info("Waiting on events before deletion") - } - return actuatorRS.WithReconcileStatus(reconcileStatus) - } - - osResource, getRS := actuator.GetResourceByComponents( + var getRS progress.ReconcileStatus + osResource, getRS = actuator.GetResourceByComponents( ctx, statusResource.RoleID, statusResource.UserID, @@ -338,17 +363,34 @@ func (r *roleassignmentReconciler) reconcileDelete(ctx context.Context, orcObjec } osResource = nil } + } + } - if osResource != nil { - log.V(logging.Info).Info("Deleting role assignment from OpenStack") - deleteRS := actuator.DeleteResource(ctx, orcObject, osResource) - if needsReschedule, _ := deleteRS.NeedsReschedule(); needsReschedule { - return deleteRS.WithReconcileStatus(reconcileStatus) - } + // If status was never populated, check for orphaned resources via adoption + if osResource == nil && orcObject.Status.Resource == nil { + resourceIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) + if canAdopt { + var err error + osResource, err = atMostOne(resourceIter, + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "found more than one matching OpenStack resource during adoption")) + if err != nil { + return reconcileStatus.WithError(err) } } } + if osResource == nil { + log.V(logging.Info).Info("Role assignment deletion confirmed") + return removeFinalizer(reconcileStatus) + } + + log.V(logging.Info).Info("Deleting role assignment from OpenStack") + deleteRS := actuator.DeleteResource(ctx, orcObject, osResource) + if needsReschedule, _ := deleteRS.NeedsReschedule(); needsReschedule { + return deleteRS.WithReconcileStatus(reconcileStatus) + } + log.V(logging.Info).Info("Role assignment deletion confirmed") return removeFinalizer(reconcileStatus) } From a028e615c1d92b11ef845782ef94ba6a9b1441d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 12:06:06 +0200 Subject: [PATCH 217/237] roleassignment: add duplicate detection to GetResourceByComponents Replace the naive 'return first result' loop with a call to atMostOne, which verifies the iterator yields at most one element. If OpenStack returns multiple results for the same (role, actor, scope) tuple, the controller now returns a terminal error instead of silently ignoring the extras. --- internal/controllers/roleassignment/actuator.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go index deb43d62f..bc29239ac 100644 --- a/internal/controllers/roleassignment/actuator.go +++ b/internal/controllers/roleassignment/actuator.go @@ -71,15 +71,13 @@ func (actuator roleassignmentActuator) GetResourceByComponents( } // Query with exact filters - should return exactly one result - for assignment, err := range actuator.osClient.ListRoleAssignments(ctx, listOpts) { - if err != nil { - return nil, progress.WrapError(err) - } - return assignment, nil + osResource, err := atMostOne(actuator.osClient.ListRoleAssignments(ctx, listOpts), + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "found more than one matching role assignment for the same (role, actor, scope) tuple")) + if err != nil { + return nil, progress.WrapError(err) } - - // Not found - return nil, nil + return osResource, nil } func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { From cb81be8479c88716a3370a3258b7c5c0c9da1bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 12:06:36 +0200 Subject: [PATCH 218/237] roleassignment: remove finalizer variable shadowing Remove local finalizer variable assignments in reconcileNormal and reconcileDelete that shadowed the package-level finalizer variable defined in zz_generated.controller.go. The local computation was redundant and could diverge if controllerName were ever changed in only one location. --- internal/controllers/roleassignment/reconciler.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go index 676e76a7f..b6a90c29f 100644 --- a/internal/controllers/roleassignment/reconciler.go +++ b/internal/controllers/roleassignment/reconciler.go @@ -125,7 +125,6 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec }() // Phase 3: Add finalizer if not present - finalizer := orcstrings.GetFinalizerName(controllerName) if !controllerutil.ContainsFinalizer(orcObject, finalizer) { patch := finalizers.SetFinalizerPatch(orcObject, finalizer) if err := r.client.Patch(ctx, orcObject, patch, client.ForceOwnership, orcstrings.GetSSAFieldOwnerWithTxn(controllerName, orcstrings.SSATransactionFinalizer)); err != nil { @@ -287,8 +286,6 @@ func (r *roleassignmentReconciler) reconcileDelete(ctx context.Context, orcObjec } }() - finalizer := orcstrings.GetFinalizerName(controllerName) - // Check if our finalizer is present var foundFinalizer bool for _, f := range orcObject.GetFinalizers() { From 0e4d1354aa1e25c7f27bbaa6848cd5b381ffc881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 12:07:44 +0200 Subject: [PATCH 219/237] roleassignment: fix deleted-resource detection in reconcileNormal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GetResourceByComponents uses a LIST query, which returns (nil, nil) for empty results rather than a 404 error. The IsNotFound(err) branch was therefore dead code: it could never trigger when a role assignment was deleted from OpenStack. The actual deletion case — status fully populated but list returns no results — fell through to the import/adoption/create phases, silently re-creating the resource instead of reporting a terminal error. Fix by removing the dead IsNotFound branch and returning a terminal error when osResource is nil after a successful query with all status components populated, matching the generic reconciler's behavior for ID-based resources. --- internal/controllers/roleassignment/reconciler.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go index b6a90c29f..2e2bd5ba0 100644 --- a/internal/controllers/roleassignment/reconciler.go +++ b/internal/controllers/roleassignment/reconciler.go @@ -157,12 +157,7 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec statusResource.ProjectID, statusResource.DomainID, ) - if needsReschedule, err := getRS.NeedsReschedule(); needsReschedule { - if orcerrors.IsNotFound(err) { - // Resource we previously created has been deleted unexpectedly - return progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "role assignment has been deleted from OpenStack")) - } + if needsReschedule, _ := getRS.NeedsReschedule(); needsReschedule { return getRS.WithReconcileStatus(reconcileStatus) } @@ -170,6 +165,13 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec log.V(logging.Verbose).Info("Got existing role assignment") return reconcileStatus } + + // Status was fully populated but the resource no longer + // exists in OpenStack. GetResourceByComponents uses a + // LIST query which returns (nil, nil) for empty results + // rather than a 404 error, so we detect deletion here. + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "role assignment has been deleted from OpenStack")) } } From 693733ac4c85fec417d58be0136b02e52f64484e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 12:08:55 +0200 Subject: [PATCH 220/237] roleassignment: extract buildListOpts to deduplicate ListAssignmentsOpts construction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same roles.ListAssignmentsOpts building pattern — conditionally setting RoleID, UserID/GroupID, ScopeProjectID/ScopeDomainID from component IDs — was repeated in GetResourceByComponents, ListOSResourcesForAdoption, ListOSResourcesForImport, and the post-create verification in CreateResource. Extract a buildListOpts helper that all four call sites now use. Also apply the atMostOne duplicate check to CreateResource's post-create verification for consistency with GetResourceByComponents. --- .../controllers/roleassignment/actuator.go | 131 ++++++------------ 1 file changed, 40 insertions(+), 91 deletions(-) diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go index bc29239ac..ee1795b6d 100644 --- a/internal/controllers/roleassignment/actuator.go +++ b/internal/controllers/roleassignment/actuator.go @@ -39,6 +39,32 @@ type roleassignmentActuator struct { k8sClient client.Client } +// buildListOpts constructs a ListAssignmentsOpts from component IDs. +// Only non-empty fields are set, so this works for both exact queries +// (all fields populated) and partial filter queries. +func buildListOpts(roleID, userID, groupID, projectID, domainID string) roles.ListAssignmentsOpts { + // Note: Don't set Effective parameter - it can cause issues with group assignments + listOpts := roles.ListAssignmentsOpts{} + + if roleID != "" { + listOpts.RoleID = roleID + } + if userID != "" { + listOpts.UserID = userID + } + if groupID != "" { + listOpts.GroupID = groupID + } + if projectID != "" { + listOpts.ScopeProjectID = projectID + } + if domainID != "" { + listOpts.ScopeDomainID = domainID + } + + return listOpts +} + // GetResourceByComponents queries for the role assignment by its tuple (role, actor, scope). // OpenStack doesn't assign IDs to role assignments - they're identified by this tuple. // Exactly one of userID/groupID must be set, and exactly one of projectID/domainID must be set. @@ -50,25 +76,7 @@ func (actuator roleassignmentActuator) GetResourceByComponents( projectID string, domainID string, ) (*osResourceT, progress.ReconcileStatus) { - // Build query options from components - listOpts := roles.ListAssignmentsOpts{ - RoleID: roleID, - // Note: Don't set Effective parameter - it can cause issues with group assignments - } - - // Set actor (user OR group, never both) - if userID != "" { - listOpts.UserID = userID - } else if groupID != "" { - listOpts.GroupID = groupID - } - - // Set scope (project OR domain, never both) - if projectID != "" { - listOpts.ScopeProjectID = projectID - } else if domainID != "" { - listOpts.ScopeDomainID = domainID - } + listOpts := buildListOpts(roleID, userID, groupID, projectID, domainID) // Query with exact filters - should return exactly one result osResource, err := atMostOne(actuator.osClient.ListRoleAssignments(ctx, listOpts), @@ -151,27 +159,7 @@ func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Co domainID = ptr.Deref(domain.Status.ID, "") } - // Build query - only set fields that have values - listOpts := roles.ListAssignmentsOpts{ - RoleID: roleID, - // Note: Don't set Effective parameter - it can cause issues with group assignments - } - - // Set actor (user OR group, never both) - if userID != "" { - listOpts.UserID = userID - } else if groupID != "" { - listOpts.GroupID = groupID - } - - // Set scope (project OR domain, never both) - if projectID != "" { - listOpts.ScopeProjectID = projectID - } else if domainID != "" { - listOpts.ScopeDomainID = domainID - } - - return actuator.osClient.ListRoleAssignments(ctx, listOpts), true + return actuator.osClient.ListRoleAssignments(ctx, buildListOpts(roleID, userID, groupID, projectID, domainID)), true } func (actuator roleassignmentActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { @@ -239,28 +227,7 @@ func (actuator roleassignmentActuator) ListOSResourcesForImport(ctx context.Cont return nil, reconcileStatus } - // Build query - only set fields that have values (filter fields are optional) - listOpts := roles.ListAssignmentsOpts{ - // Note: Don't set Effective parameter - it can cause issues with group assignments - } - - if roleID != "" { - listOpts.RoleID = roleID - } - if userID != "" { - listOpts.UserID = userID - } - if groupID != "" { - listOpts.GroupID = groupID - } - if projectID != "" { - listOpts.ScopeProjectID = projectID - } - if domainID != "" { - listOpts.ScopeDomainID = domainID - } - - return actuator.osClient.ListRoleAssignments(ctx, listOpts), nil + return actuator.osClient.ListRoleAssignments(ctx, buildListOpts(roleID, userID, groupID, projectID, domainID)), nil } func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -355,37 +322,19 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o } // Verify the assignment was created by listing with exact filters - listOpts := roles.ListAssignmentsOpts{ - RoleID: roleID, - // Note: Don't set Effective parameter - it can cause issues with group assignments - } - - // Set actor (user OR group, never both) - if userID != "" { - listOpts.UserID = userID - } else if groupID != "" { - listOpts.GroupID = groupID - } - - // Set scope (project OR domain, never both) - if projectID != "" { - listOpts.ScopeProjectID = projectID - } else if domainID != "" { - listOpts.ScopeDomainID = domainID + osResource, verifyErr := atMostOne(actuator.osClient.ListRoleAssignments(ctx, buildListOpts(roleID, userID, groupID, projectID, domainID)), + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "found more than one matching role assignment after creation")) + if verifyErr != nil { + return nil, progress.WrapError(verifyErr) } - - // Get the first matching assignment to return - for assignment, err := range actuator.osClient.ListRoleAssignments(ctx, listOpts) { - if err != nil { - return nil, progress.WrapError(err) - } - return assignment, nil + if osResource == nil { + // This shouldn't happen - we just assigned it + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "role assignment succeeded but could not be found in OpenStack")) } - - // This shouldn't happen - we just assigned it - return nil, progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, - "role assignment succeeded but could not be found in OpenStack")) + return osResource, nil } func (actuator roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { From cbc0e2d6c01ba754f992ce204a15be0efa38ae95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 14:02:53 +0200 Subject: [PATCH 221/237] roleassignment: fix ListOSResourcesForAdoption ignoring FetchDependency errors FetchDependency always returns a non-nil pointer (new(T)), so the nil checks on the returned object were dead code. When a dependency was not found or not ready, the code proceeded with empty-string IDs, causing buildListOpts to produce a partially-filtered or unfiltered OpenStack query that could match unrelated role assignments. Check ReconcileStatus.NeedsReschedule() instead, which correctly detects not-found, not-ready, and error states. This also fixes the reconcileDelete path which calls ListOSResourcesForAdoption to find orphaned resources. Add unit tests for role-not-found, user-not-ready, and project-not-found cases to prevent regression. --- .../controllers/roleassignment/actuator.go | 20 +++--- .../roleassignment/actuator_test.go | 64 +++++++++++++++++++ 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go index ee1795b6d..d988815e4 100644 --- a/internal/controllers/roleassignment/actuator.go +++ b/internal/controllers/roleassignment/actuator.go @@ -98,37 +98,37 @@ func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Co var roleID, userID, groupID, projectID, domainID string // Role dependency (required) - role, _ := dependency.FetchDependency( + role, rs := dependency.FetchDependency( ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.RoleRef, "Role", func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - if role == nil { + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { return nil, false // Not ready } roleID = ptr.Deref(role.Status.ID, "") // Actor dependency (user XOR group) if resourceSpec.UserRef != nil { - user, _ := dependency.FetchDependency( + user, rs := dependency.FetchDependency( ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.UserRef, "User", func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - if user == nil { + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { return nil, false // Not ready } userID = ptr.Deref(user.Status.ID, "") } else { - group, _ := dependency.FetchDependency( + group, rs := dependency.FetchDependency( ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.GroupRef, "Group", func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - if group == nil { + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { return nil, false // Not ready } groupID = ptr.Deref(group.Status.ID, "") @@ -136,24 +136,24 @@ func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Co // Scope dependency (project XOR domain) if resourceSpec.ProjectRef != nil { - project, _ := dependency.FetchDependency( + project, rs := dependency.FetchDependency( ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - if project == nil { + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { return nil, false // Not ready } projectID = ptr.Deref(project.Status.ID, "") } else { - domain, _ := dependency.FetchDependency( + domain, rs := dependency.FetchDependency( ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, ) - if domain == nil { + if needsReschedule, _ := rs.NeedsReschedule(); needsReschedule { return nil, false // Not ready } domainID = ptr.Deref(domain.Status.ID, "") diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go index 723c2f9c0..d171490bf 100644 --- a/internal/controllers/roleassignment/actuator_test.go +++ b/internal/controllers/roleassignment/actuator_test.go @@ -291,6 +291,70 @@ func TestListOSResourcesForAdoption(t *testing.T) { wantAdopt: true, checks: checks(noError, findsN(0)), }, + { + name: "role dependency not found, returns false", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "missing-role", + UserRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-user"), + ProjectRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-project"), + }, + }, + }, + k8sObjects: []client.Object{ + // role is missing + availableUser("test-user", "user-id-1"), + availableProject("test-project", "project-id-1"), + }, + // OS client has a match — must NOT be queried + osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{userProjectAssignment}}, + wantAdopt: false, + }, + { + name: "user dependency not ready, returns false", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "test-role", + UserRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-user"), + ProjectRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-project"), + }, + }, + }, + k8sObjects: []client.Object{ + availableRole("test-role", "role-id-1"), + // user exists but is not available (no Available condition, no Status.ID) + &orcv1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{Name: "test-user", Namespace: testNamespace}, + }, + availableProject("test-project", "project-id-1"), + }, + osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{userProjectAssignment}}, + wantAdopt: false, + }, + { + name: "project dependency not found, returns false", + orcObject: &orcv1alpha1.RoleAssignment{ + ObjectMeta: metav1.ObjectMeta{Name: "test-ra", Namespace: testNamespace}, + Spec: orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{ + RoleRef: "test-role", + GroupRef: ptr.To[orcv1alpha1.KubernetesNameRef]("test-group"), + ProjectRef: ptr.To[orcv1alpha1.KubernetesNameRef]("missing-project"), + }, + }, + }, + k8sObjects: []client.Object{ + availableRole("test-role", "role-id-2"), + availableGroup("test-group", "group-id-2"), + // project is missing + }, + osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{groupDomainAssignment}}, + wantAdopt: false, + }, { name: "OS client returns error", orcObject: &orcv1alpha1.RoleAssignment{ From fa895d8bf7d7c8ef91b5bc477dbfb5d9ad3c6314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 15:29:50 +0200 Subject: [PATCH 222/237] Generate OLM bundle Run `make generate-bundle` which is now required in CI. --- .../bases/orc.clusterserviceversion.yaml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/config/manifests/bases/orc.clusterserviceversion.yaml b/config/manifests/bases/orc.clusterserviceversion.yaml index 0b7164e78..ef49c8611 100644 --- a/config/manifests/bases/orc.clusterserviceversion.yaml +++ b/config/manifests/bases/orc.clusterserviceversion.yaml @@ -19,6 +19,16 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - description: AddressScope is the Schema for an ORC resource. + displayName: Address Scope + kind: AddressScope + name: addressscopes.openstack.k-orc.cloud + version: v1alpha1 + - description: ApplicationCredential is the Schema for an ORC resource. + displayName: Application Credential + kind: ApplicationCredential + name: applicationcredentials.openstack.k-orc.cloud + version: v1alpha1 - description: Domain is the Schema for an ORC resource. displayName: Domain kind: Domain @@ -69,6 +79,11 @@ spec: kind: Project name: projects.openstack.k-orc.cloud version: v1alpha1 + - description: RoleAssignment is the Schema for an ORC resource. + displayName: Role Assignment + kind: RoleAssignment + name: roleassignments.openstack.k-orc.cloud + version: v1alpha1 - description: Role is the Schema for an ORC resource. displayName: Role kind: Role @@ -109,6 +124,16 @@ spec: kind: Subnet name: subnets.openstack.k-orc.cloud version: v1alpha1 + - description: Trunk is the Schema for an ORC resource. + displayName: Trunk + kind: Trunk + name: trunks.openstack.k-orc.cloud + version: v1alpha1 + - description: User is the Schema for an ORC resource. + displayName: User + kind: User + name: users.openstack.k-orc.cloud + version: v1alpha1 - description: Volume is the Schema for an ORC resource. displayName: Volume kind: Volume From f9bcdef4c47a426a071f830f32e0a4c22cd190fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Thu, 2 Jul 2026 15:39:05 +0200 Subject: [PATCH 223/237] Fix unparam lint errors in roleassignment actuator tests Remove the name parameter from helper functions availableRole, availableUser, availableGroup, availableProject, and availableDomain since each was always called with the same value. The names are now defined as constants and used directly in the helpers. Also vary statusID values in the 'no matches from OS' test case to avoid secondary unparam warnings for availableUser and availableProject. --- .../roleassignment/actuator_test.go | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go index d171490bf..2ddc49467 100644 --- a/internal/controllers/roleassignment/actuator_test.go +++ b/internal/controllers/roleassignment/actuator_test.go @@ -39,7 +39,14 @@ var ( errTest = errors.New("test error") ) -const testNamespace = "test-ns" +const ( + testNamespace = "test-ns" + testRoleName = "test-role" + testUserName = "test-user" + testGroupName = "test-group" + testProjectName = "test-project" + testDomainName = "test-domain" +) // mockRoleAssignmentClient is a simple mock that returns pre-configured assignments. type mockRoleAssignmentClient struct { @@ -129,10 +136,10 @@ func newFakeK8sClient(objects ...client.Object) client.Client { } // availableRole returns a Role object that is available with the given status ID. -func availableRole(name, statusID string) *orcv1alpha1.Role { +func availableRole(statusID string) *orcv1alpha1.Role { return &orcv1alpha1.Role{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: testRoleName, Namespace: testNamespace, }, Status: orcv1alpha1.RoleStatus{ @@ -143,10 +150,10 @@ func availableRole(name, statusID string) *orcv1alpha1.Role { } // availableUser returns a User object that is available with the given status ID. -func availableUser(name, statusID string) *orcv1alpha1.User { +func availableUser(statusID string) *orcv1alpha1.User { return &orcv1alpha1.User{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: testUserName, Namespace: testNamespace, }, Status: orcv1alpha1.UserStatus{ @@ -157,10 +164,10 @@ func availableUser(name, statusID string) *orcv1alpha1.User { } // availableGroup returns a Group object that is available with the given status ID. -func availableGroup(name, statusID string) *orcv1alpha1.Group { +func availableGroup(statusID string) *orcv1alpha1.Group { return &orcv1alpha1.Group{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: testGroupName, Namespace: testNamespace, }, Status: orcv1alpha1.GroupStatus{ @@ -171,10 +178,10 @@ func availableGroup(name, statusID string) *orcv1alpha1.Group { } // availableProject returns a Project object that is available with the given status ID. -func availableProject(name, statusID string) *orcv1alpha1.Project { +func availableProject(statusID string) *orcv1alpha1.Project { return &orcv1alpha1.Project{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: testProjectName, Namespace: testNamespace, }, Status: orcv1alpha1.ProjectStatus{ @@ -185,10 +192,10 @@ func availableProject(name, statusID string) *orcv1alpha1.Project { } // availableDomain returns a Domain object that is available with the given status ID. -func availableDomain(name, statusID string) *orcv1alpha1.Domain { +func availableDomain(statusID string) *orcv1alpha1.Domain { return &orcv1alpha1.Domain{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: testDomainName, Namespace: testNamespace, }, Status: orcv1alpha1.DomainStatus{ @@ -241,9 +248,9 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, }, k8sObjects: []client.Object{ - availableRole("test-role", "role-id-1"), - availableUser("test-user", "user-id-1"), - availableProject("test-project", "project-id-1"), + availableRole("role-id-1"), + availableUser("user-id-1"), + availableProject("project-id-1"), }, osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{userProjectAssignment}}, wantAdopt: true, @@ -262,9 +269,9 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, }, k8sObjects: []client.Object{ - availableRole("test-role", "role-id-2"), - availableGroup("test-group", "group-id-2"), - availableDomain("test-domain", "domain-id-2"), + availableRole("role-id-2"), + availableGroup("group-id-2"), + availableDomain("domain-id-2"), }, osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{groupDomainAssignment}}, wantAdopt: true, @@ -283,9 +290,9 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, }, k8sObjects: []client.Object{ - availableRole("test-role", "role-id-1"), - availableUser("test-user", "user-id-1"), - availableProject("test-project", "project-id-1"), + availableRole("role-id-1"), + availableUser("user-id-2"), + availableProject("project-id-2"), }, osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{}}, wantAdopt: true, @@ -305,8 +312,8 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, k8sObjects: []client.Object{ // role is missing - availableUser("test-user", "user-id-1"), - availableProject("test-project", "project-id-1"), + availableUser("user-id-1"), + availableProject("project-id-1"), }, // OS client has a match — must NOT be queried osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{userProjectAssignment}}, @@ -325,12 +332,12 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, }, k8sObjects: []client.Object{ - availableRole("test-role", "role-id-1"), + availableRole("role-id-1"), // user exists but is not available (no Available condition, no Status.ID) &orcv1alpha1.User{ ObjectMeta: metav1.ObjectMeta{Name: "test-user", Namespace: testNamespace}, }, - availableProject("test-project", "project-id-1"), + availableProject("project-id-1"), }, osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{userProjectAssignment}}, wantAdopt: false, @@ -348,8 +355,8 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, }, k8sObjects: []client.Object{ - availableRole("test-role", "role-id-2"), - availableGroup("test-group", "group-id-2"), + availableRole("role-id-2"), + availableGroup("group-id-2"), // project is missing }, osClient: mockRoleAssignmentClient{assignments: []roles.RoleAssignment{groupDomainAssignment}}, @@ -368,9 +375,9 @@ func TestListOSResourcesForAdoption(t *testing.T) { }, }, k8sObjects: []client.Object{ - availableRole("test-role", "role-id-1"), - availableUser("test-user", "user-id-1"), - availableProject("test-project", "project-id-1"), + availableRole("role-id-1"), + availableUser("user-id-1"), + availableProject("project-id-1"), }, osClient: osclients.NewRoleAssignmentErrorClient(errTest), wantAdopt: true, From de8252304f7c7ed232e516bd0ed725eaa4ce1a2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:14:34 +0000 Subject: [PATCH 224/237] :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.31.0 to 2.32.0 - [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.31.0...v2.32.0) Updates `github.com/onsi/gomega` from 1.42.0 to 1.42.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.42.0...v1.42.1) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.32.0 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.42.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 | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index e7597c3b9..9914ebd13 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/google/go-cmp v0.7.0 github.com/gophercloud/gophercloud/v2 v2.13.0 github.com/gophercloud/utils/v2 v2.0.0-20241220104409-2e0af06694a1 - github.com/onsi/ginkgo/v2 v2.31.0 - github.com/onsi/gomega v1.42.0 + github.com/onsi/ginkgo/v2 v2.32.0 + github.com/onsi/gomega v1.42.1 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 golang.org/x/text v0.38.0 @@ -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.36.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/term v0.43.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.45.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect diff --git a/go.sum b/go.sum index c94d79f96..2d3765b95 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.31.0 h1:GtuJos5DFUV9EerYJo8RhYxosYNGvOdDE5haKq6Grfs= -github.com/onsi/ginkgo/v2 v2.31.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= -github.com/onsi/gomega v1.42.0 h1:CJby8u36xb7v34W78F8WKvqTQP7PCMIPB78IVDB73l4= -github.com/onsi/gomega v1.42.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= +github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E= +github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= +github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= 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= @@ -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.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= 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.21.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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= 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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= From 95c86f44150c87f631217677e17dac7a35fe89b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:15:37 +0000 Subject: [PATCH 225/237] :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.4.0 to 6.5.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/4a3601121dd01d1626a1e23e37211e3254c1c06c...924ae3a1cded613372ab5595356fb5720e22ba16) Updates `actions/cache` from 5.0.5 to 6.1.0 - [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/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions - dependency-name: actions/cache dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/go-lint.yaml | 2 +- .github/workflows/pr-dependabot.yaml | 4 ++-- .github/workflows/semver.yaml | 2 +- .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 ecc415179..ef2269990 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index a18068ff3..db063ac2f 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -26,10 +26,10 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 with: go-version: ${{ steps.vars.outputs.go_version }} - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # tag=v5.0.5 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 name: Restore go cache with: path: | diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 60e141a06..37d67e203 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 0a511fb3c..625b525d2 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.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 efabf9d56..a95d4c81e 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@4a3601121dd01d1626a1e23e37211e3254c1c06c # tag=v6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 with: go-version: ${{ steps.vars.outputs.go_version }} - name: Run verify security target From 5502581d9259f1fd54958b28a4488f72a9ed2745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 7 Jul 2026 13:56:03 +0200 Subject: [PATCH 226/237] hack/collectlogs: make ORC pod log collection more robust Resolve the pod name explicitly instead of relying on a label selector, capture stderr from kubectl logs into the log file, and detect empty log files with a --previous fallback. Closes #839 --- hack/collectlogs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hack/collectlogs b/hack/collectlogs index 2abb930dc..e41e524ad 100755 --- a/hack/collectlogs +++ b/hack/collectlogs @@ -27,7 +27,14 @@ done cp ./devstack/local.conf "$DEVSTACK_LOG_DIR" kubectl describe pods -n orc-system > "$LOG_DIR/orc-pod.txt" -kubectl logs -n orc-system -l control-plane=controller-manager --tail=-1 > "$LOG_DIR/orc-pod.log" + +ORC_POD=$(kubectl get pods -n orc-system -l control-plane=controller-manager -o jsonpath='{.items[0].metadata.name}') +kubectl logs -n orc-system "$ORC_POD" --tail=-1 > "$LOG_DIR/orc-pod.log" 2>&1 + +if [ ! -s "$LOG_DIR/orc-pod.log" ]; then + echo "WARNING: orc-pod.log is empty, trying --previous" >&2 + kubectl logs -n orc-system "$ORC_POD" --previous > "$LOG_DIR/orc-pod-previous.log" 2>&1 || true +fi kubectl get -n orc-system all -o yaml > "$LOG_DIR/orc-resources.yaml" From 7b1c99d4bd5270d48c1e2d479e348d936f3537fe Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 8 Jul 2026 17:18:35 +0300 Subject: [PATCH 227/237] Add resync API fields and generated clients Introduce per-resource resync configuration and last sync status across the API surface. The new spec field lets users opt individual resources into periodic reconciliation while status.lastSyncTime records the last successful OpenStack sync. Regenerate CRDs, OpenAPI, apply configurations, and controller adapters so the new fields are available consistently to controllers, clients, and generated documentation. --- api/v1alpha1/router_interface_types.go | 19 +- .../zz_generated.addressscope-resource.go | 15 + ...enerated.applicationcredential-resource.go | 15 + api/v1alpha1/zz_generated.deepcopy.go | 234 ++++++++++ api/v1alpha1/zz_generated.domain-resource.go | 15 + .../zz_generated.endpoint-resource.go | 15 + api/v1alpha1/zz_generated.flavor-resource.go | 15 + .../zz_generated.floatingip-resource.go | 15 + api/v1alpha1/zz_generated.group-resource.go | 15 + api/v1alpha1/zz_generated.image-resource.go | 15 + api/v1alpha1/zz_generated.keypair-resource.go | 15 + api/v1alpha1/zz_generated.network-resource.go | 15 + api/v1alpha1/zz_generated.port-resource.go | 15 + api/v1alpha1/zz_generated.project-resource.go | 15 + api/v1alpha1/zz_generated.role-resource.go | 15 + .../zz_generated.roleassignment-resource.go | 15 + api/v1alpha1/zz_generated.router-resource.go | 15 + .../zz_generated.securitygroup-resource.go | 15 + api/v1alpha1/zz_generated.server-resource.go | 15 + .../zz_generated.servergroup-resource.go | 15 + api/v1alpha1/zz_generated.service-resource.go | 15 + .../zz_generated.sharenetwork-resource.go | 15 + api/v1alpha1/zz_generated.subnet-resource.go | 15 + api/v1alpha1/zz_generated.trunk-resource.go | 15 + api/v1alpha1/zz_generated.user-resource.go | 15 + api/v1alpha1/zz_generated.volume-resource.go | 15 + .../zz_generated.volumetype-resource.go | 15 + cmd/models-schema/zz_generated.openapi.go | 416 +++++++++++++++--- cmd/resource-generator/data/adapter.template | 10 + cmd/resource-generator/data/api.template | 15 + .../openstack.k-orc.cloud_addressscopes.yaml | 16 + ...ck.k-orc.cloud_applicationcredentials.yaml | 16 + .../bases/openstack.k-orc.cloud_domains.yaml | 16 + .../openstack.k-orc.cloud_endpoints.yaml | 16 + .../bases/openstack.k-orc.cloud_flavors.yaml | 16 + .../openstack.k-orc.cloud_floatingips.yaml | 16 + .../bases/openstack.k-orc.cloud_groups.yaml | 16 + .../bases/openstack.k-orc.cloud_images.yaml | 16 + .../bases/openstack.k-orc.cloud_keypairs.yaml | 16 + .../bases/openstack.k-orc.cloud_networks.yaml | 16 + .../bases/openstack.k-orc.cloud_ports.yaml | 16 + .../bases/openstack.k-orc.cloud_projects.yaml | 16 + ...openstack.k-orc.cloud_roleassignments.yaml | 16 + .../bases/openstack.k-orc.cloud_roles.yaml | 16 + ...penstack.k-orc.cloud_routerinterfaces.yaml | 23 +- .../bases/openstack.k-orc.cloud_routers.yaml | 16 + .../openstack.k-orc.cloud_securitygroups.yaml | 16 + .../openstack.k-orc.cloud_servergroups.yaml | 16 + .../bases/openstack.k-orc.cloud_servers.yaml | 16 + .../bases/openstack.k-orc.cloud_services.yaml | 16 + .../openstack.k-orc.cloud_sharenetworks.yaml | 16 + .../bases/openstack.k-orc.cloud_subnets.yaml | 16 + .../bases/openstack.k-orc.cloud_trunks.yaml | 16 + .../bases/openstack.k-orc.cloud_users.yaml | 16 + .../bases/openstack.k-orc.cloud_volumes.yaml | 16 + .../openstack.k-orc.cloud_volumetypes.yaml | 16 + .../addressscope/zz_generated.adapter.go | 10 + .../zz_generated.adapter.go | 10 + .../domain/zz_generated.adapter.go | 10 + .../endpoint/zz_generated.adapter.go | 10 + .../flavor/zz_generated.adapter.go | 10 + .../floatingip/zz_generated.adapter.go | 10 + .../controllers/generic/interfaces/adapter.go | 2 + .../controllers/group/zz_generated.adapter.go | 10 + .../controllers/image/zz_generated.adapter.go | 10 + .../keypair/zz_generated.adapter.go | 10 + .../network/zz_generated.adapter.go | 10 + .../controllers/port/zz_generated.adapter.go | 10 + .../project/zz_generated.adapter.go | 10 + .../controllers/role/zz_generated.adapter.go | 10 + .../roleassignment/zz_generated.adapter.go | 10 + .../router/zz_generated.adapter.go | 10 + .../securitygroup/zz_generated.adapter.go | 10 + .../server/zz_generated.adapter.go | 10 + .../servergroup/zz_generated.adapter.go | 10 + .../service/zz_generated.adapter.go | 10 + .../sharenetwork/zz_generated.adapter.go | 10 + .../subnet/zz_generated.adapter.go | 10 + .../controllers/trunk/zz_generated.adapter.go | 10 + .../controllers/user/zz_generated.adapter.go | 10 + .../volume/zz_generated.adapter.go | 10 + .../volumetype/zz_generated.adapter.go | 10 + .../api/v1alpha1/addressscopespec.go | 10 + .../api/v1alpha1/addressscopestatus.go | 16 +- .../api/v1alpha1/applicationcredentialspec.go | 10 + .../v1alpha1/applicationcredentialstatus.go | 16 +- .../api/v1alpha1/domainspec.go | 10 + .../api/v1alpha1/domainstatus.go | 16 +- .../api/v1alpha1/endpointspec.go | 10 + .../api/v1alpha1/endpointstatus.go | 16 +- .../api/v1alpha1/flavorspec.go | 10 + .../api/v1alpha1/flavorstatus.go | 16 +- .../api/v1alpha1/floatingipspec.go | 10 + .../api/v1alpha1/floatingipstatus.go | 16 +- .../api/v1alpha1/groupspec.go | 10 + .../api/v1alpha1/groupstatus.go | 16 +- .../api/v1alpha1/imagespec.go | 10 + .../api/v1alpha1/imagestatus.go | 10 + .../api/v1alpha1/keypairspec.go | 10 + .../api/v1alpha1/keypairstatus.go | 16 +- .../api/v1alpha1/networkspec.go | 10 + .../api/v1alpha1/networkstatus.go | 16 +- .../api/v1alpha1/portspec.go | 10 + .../api/v1alpha1/portstatus.go | 16 +- .../api/v1alpha1/projectspec.go | 10 + .../api/v1alpha1/projectstatus.go | 16 +- .../api/v1alpha1/roleassignmentspec.go | 10 + .../api/v1alpha1/roleassignmentstatus.go | 14 +- .../api/v1alpha1/rolespec.go | 10 + .../api/v1alpha1/rolestatus.go | 16 +- .../api/v1alpha1/routerinterfacespec.go | 16 +- .../api/v1alpha1/routerinterfacestatus.go | 14 +- .../api/v1alpha1/routerspec.go | 10 + .../api/v1alpha1/routerstatus.go | 16 +- .../api/v1alpha1/securitygroupspec.go | 10 + .../api/v1alpha1/securitygroupstatus.go | 16 +- .../api/v1alpha1/servergroupspec.go | 10 + .../api/v1alpha1/servergroupstatus.go | 16 +- .../api/v1alpha1/serverspec.go | 10 + .../api/v1alpha1/serverstatus.go | 16 +- .../api/v1alpha1/servicespec.go | 10 + .../api/v1alpha1/servicestatus.go | 16 +- .../api/v1alpha1/sharenetworkspec.go | 10 + .../api/v1alpha1/sharenetworkstatus.go | 16 +- .../api/v1alpha1/subnetspec.go | 10 + .../api/v1alpha1/subnetstatus.go | 16 +- .../api/v1alpha1/trunkspec.go | 10 + .../api/v1alpha1/trunkstatus.go | 16 +- .../api/v1alpha1/userspec.go | 10 + .../api/v1alpha1/userstatus.go | 16 +- .../api/v1alpha1/volumespec.go | 10 + .../api/v1alpha1/volumestatus.go | 16 +- .../api/v1alpha1/volumetypespec.go | 10 + .../api/v1alpha1/volumetypestatus.go | 16 +- .../applyconfiguration/internal/internal.go | 158 +++++++ website/docs/crd-reference.md | 52 +++ 136 files changed, 2495 insertions(+), 131 deletions(-) diff --git a/api/v1alpha1/router_interface_types.go b/api/v1alpha1/router_interface_types.go index 236c9bebf..2506e5610 100644 --- a/api/v1alpha1/router_interface_types.go +++ b/api/v1alpha1/router_interface_types.go @@ -74,7 +74,9 @@ const ( ) // +kubebuilder:validation:XValidation:rule="self.type == 'Subnet' ? has(self.subnetRef) : !has(self.subnetRef)",message="subnetRef is required when type is 'Subnet' and not permitted otherwise" -// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="RouterInterfaceResourceSpec is immutable" +// +kubebuilder:validation:XValidation:rule="self.type == oldSelf.type",message="type is immutable" +// +kubebuilder:validation:XValidation:rule="self.routerRef == oldSelf.routerRef",message="routerRef is immutable" +// +kubebuilder:validation:XValidation:rule="has(self.subnetRef) == has(oldSelf.subnetRef) && (!has(self.subnetRef) || self.subnetRef == oldSelf.subnetRef)",message="subnetRef is immutable" type RouterInterfaceSpec struct { // type specifies the type of the router interface. // +required @@ -89,6 +91,14 @@ type RouterInterfaceSpec struct { // +unionMember // +optional SubnetRef *KubernetesNameRef `json:"subnetRef,omitempty"` + + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config } type RouterInterfaceStatus struct { @@ -118,9 +128,14 @@ type RouterInterfaceStatus struct { // +kubebuilder:validation:MaxLength=1024 // +optional ID *string `json:"id,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // of the resource. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } -var _ ObjectWithConditions = &Router{} +var _ ObjectWithConditions = &RouterInterface{} func (i *RouterInterface) GetConditions() []metav1.Condition { return i.Status.Conditions diff --git a/api/v1alpha1/zz_generated.addressscope-resource.go b/api/v1alpha1/zz_generated.addressscope-resource.go index a61636c7d..2ed71b9dd 100644 --- a/api/v1alpha1/zz_generated.addressscope-resource.go +++ b/api/v1alpha1/zz_generated.addressscope-resource.go @@ -75,6 +75,14 @@ type AddressScopeSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type AddressScopeStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *AddressScopeResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &AddressScope{} diff --git a/api/v1alpha1/zz_generated.applicationcredential-resource.go b/api/v1alpha1/zz_generated.applicationcredential-resource.go index d949c84d0..36058ef73 100644 --- a/api/v1alpha1/zz_generated.applicationcredential-resource.go +++ b/api/v1alpha1/zz_generated.applicationcredential-resource.go @@ -75,6 +75,14 @@ type ApplicationCredentialSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type ApplicationCredentialStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *ApplicationCredentialResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &ApplicationCredential{} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bb7d1b67d..4e7d7e3c1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -227,6 +227,11 @@ func (in *AddressScopeSpec) DeepCopyInto(out *AddressScopeSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -260,6 +265,10 @@ func (in *AddressScopeStatus) DeepCopyInto(out *AddressScopeStatus) { *out = new(AddressScopeResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddressScopeStatus. @@ -638,6 +647,11 @@ func (in *ApplicationCredentialSpec) DeepCopyInto(out *ApplicationCredentialSpec *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -671,6 +685,10 @@ func (in *ApplicationCredentialStatus) DeepCopyInto(out *ApplicationCredentialSt *out = new(ApplicationCredentialResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationCredentialStatus. @@ -875,6 +893,11 @@ func (in *DomainSpec) DeepCopyInto(out *DomainSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -908,6 +931,10 @@ func (in *DomainStatus) DeepCopyInto(out *DomainStatus) { *out = new(DomainResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DomainStatus. @@ -1087,6 +1114,11 @@ func (in *EndpointSpec) DeepCopyInto(out *EndpointSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -1120,6 +1152,10 @@ func (in *EndpointStatus) DeepCopyInto(out *EndpointStatus) { *out = new(EndpointResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointStatus. @@ -1534,6 +1570,11 @@ func (in *FlavorSpec) DeepCopyInto(out *FlavorSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -1567,6 +1608,10 @@ func (in *FlavorStatus) DeepCopyInto(out *FlavorStatus) { *out = new(FlavorResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorStatus. @@ -1798,6 +1843,11 @@ func (in *FloatingIPSpec) DeepCopyInto(out *FloatingIPSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -1831,6 +1881,10 @@ func (in *FloatingIPStatus) DeepCopyInto(out *FloatingIPStatus) { *out = new(FloatingIPResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FloatingIPStatus. @@ -2015,6 +2069,11 @@ func (in *GroupSpec) DeepCopyInto(out *GroupSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -2048,6 +2107,10 @@ func (in *GroupStatus) DeepCopyInto(out *GroupStatus) { *out = new(GroupResourceStatus) **out = **in } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupStatus. @@ -2542,6 +2605,11 @@ func (in *ImageSpec) DeepCopyInto(out *ImageSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -2575,6 +2643,10 @@ func (in *ImageStatus) DeepCopyInto(out *ImageStatus) { *out = new(ImageResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } in.ImageStatusExtra.DeepCopyInto(&out.ImageStatusExtra) } @@ -2770,6 +2842,11 @@ func (in *KeyPairSpec) DeepCopyInto(out *KeyPairSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -2803,6 +2880,10 @@ func (in *KeyPairStatus) DeepCopyInto(out *KeyPairStatus) { *out = new(KeyPairResourceStatus) **out = **in } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyPairStatus. @@ -3099,6 +3180,11 @@ func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -3132,6 +3218,10 @@ func (in *NetworkStatus) DeepCopyInto(out *NetworkStatus) { *out = new(NetworkResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkStatus. @@ -3477,6 +3567,11 @@ func (in *PortSpec) DeepCopyInto(out *PortSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -3510,6 +3605,10 @@ func (in *PortStatus) DeepCopyInto(out *PortStatus) { *out = new(PortResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortStatus. @@ -3735,6 +3834,11 @@ func (in *ProjectSpec) DeepCopyInto(out *ProjectSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -3768,6 +3872,10 @@ func (in *ProjectStatus) DeepCopyInto(out *ProjectStatus) { *out = new(ProjectResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectStatus. @@ -4014,6 +4122,11 @@ func (in *RoleAssignmentSpec) DeepCopyInto(out *RoleAssignmentSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -4042,6 +4155,10 @@ func (in *RoleAssignmentStatus) DeepCopyInto(out *RoleAssignmentStatus) { *out = new(RoleAssignmentResourceStatus) **out = **in } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentStatus. @@ -4199,6 +4316,11 @@ func (in *RoleSpec) DeepCopyInto(out *RoleSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -4232,6 +4354,10 @@ func (in *RoleStatus) DeepCopyInto(out *RoleStatus) { *out = new(RoleResourceStatus) **out = **in } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleStatus. @@ -4394,6 +4520,11 @@ func (in *RouterInterfaceSpec) DeepCopyInto(out *RouterInterfaceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouterInterfaceSpec. @@ -4421,6 +4552,10 @@ func (in *RouterInterfaceStatus) DeepCopyInto(out *RouterInterfaceStatus) { *out = new(string) **out = **in } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouterInterfaceStatus. @@ -4573,6 +4708,11 @@ func (in *RouterSpec) DeepCopyInto(out *RouterSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -4606,6 +4746,10 @@ func (in *RouterStatus) DeepCopyInto(out *RouterStatus) { *out = new(RouterResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouterStatus. @@ -4886,6 +5030,11 @@ func (in *SecurityGroupSpec) DeepCopyInto(out *SecurityGroupSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -4919,6 +5068,10 @@ func (in *SecurityGroupStatus) DeepCopyInto(out *SecurityGroupStatus) { *out = new(SecurityGroupResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityGroupStatus. @@ -5201,6 +5354,11 @@ func (in *ServerGroupSpec) DeepCopyInto(out *ServerGroupSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -5234,6 +5392,10 @@ func (in *ServerGroupStatus) DeepCopyInto(out *ServerGroupStatus) { *out = new(ServerGroupResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerGroupStatus. @@ -5569,6 +5731,11 @@ func (in *ServerSpec) DeepCopyInto(out *ServerSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -5602,6 +5769,10 @@ func (in *ServerStatus) DeepCopyInto(out *ServerStatus) { *out = new(ServerResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerStatus. @@ -5826,6 +5997,11 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -5859,6 +6035,10 @@ func (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) { *out = new(ServiceResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus. @@ -6066,6 +6246,11 @@ func (in *ShareNetworkSpec) DeepCopyInto(out *ShareNetworkSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -6099,6 +6284,10 @@ func (in *ShareNetworkStatus) DeepCopyInto(out *ShareNetworkStatus) { *out = new(ShareNetworkResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShareNetworkStatus. @@ -6410,6 +6599,11 @@ func (in *SubnetSpec) DeepCopyInto(out *SubnetSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -6443,6 +6637,10 @@ func (in *SubnetStatus) DeepCopyInto(out *SubnetStatus) { *out = new(SubnetResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetStatus. @@ -6674,6 +6872,11 @@ func (in *TrunkSpec) DeepCopyInto(out *TrunkSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -6707,6 +6910,10 @@ func (in *TrunkStatus) DeepCopyInto(out *TrunkStatus) { *out = new(TrunkResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrunkStatus. @@ -6956,6 +7163,11 @@ func (in *UserSpec) DeepCopyInto(out *UserSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -6989,6 +7201,10 @@ func (in *UserStatus) DeepCopyInto(out *UserStatus) { *out = new(UserResourceStatus) **out = **in } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. @@ -7277,6 +7493,11 @@ func (in *VolumeSpec) DeepCopyInto(out *VolumeSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -7310,6 +7531,10 @@ func (in *VolumeStatus) DeepCopyInto(out *VolumeStatus) { *out = new(VolumeResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeStatus. @@ -7544,6 +7769,11 @@ func (in *VolumeTypeSpec) DeepCopyInto(out *VolumeTypeSpec) { *out = new(ManagedOptions) **out = **in } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(v1.Duration) + **out = **in + } out.CloudCredentialsRef = in.CloudCredentialsRef } @@ -7577,6 +7807,10 @@ func (in *VolumeTypeStatus) DeepCopyInto(out *VolumeTypeStatus) { *out = new(VolumeTypeResourceStatus) (*in).DeepCopyInto(*out) } + if in.LastSyncTime != nil { + in, out := &in.LastSyncTime, &out.LastSyncTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeTypeStatus. diff --git a/api/v1alpha1/zz_generated.domain-resource.go b/api/v1alpha1/zz_generated.domain-resource.go index ae2e5fc4e..7e42102bb 100644 --- a/api/v1alpha1/zz_generated.domain-resource.go +++ b/api/v1alpha1/zz_generated.domain-resource.go @@ -75,6 +75,14 @@ type DomainSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type DomainStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *DomainResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Domain{} diff --git a/api/v1alpha1/zz_generated.endpoint-resource.go b/api/v1alpha1/zz_generated.endpoint-resource.go index 0fcc28d2d..125d1515f 100644 --- a/api/v1alpha1/zz_generated.endpoint-resource.go +++ b/api/v1alpha1/zz_generated.endpoint-resource.go @@ -75,6 +75,14 @@ type EndpointSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type EndpointStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *EndpointResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Endpoint{} diff --git a/api/v1alpha1/zz_generated.flavor-resource.go b/api/v1alpha1/zz_generated.flavor-resource.go index 6ae9d1fd8..038895f18 100644 --- a/api/v1alpha1/zz_generated.flavor-resource.go +++ b/api/v1alpha1/zz_generated.flavor-resource.go @@ -75,6 +75,14 @@ type FlavorSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type FlavorStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *FlavorResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Flavor{} diff --git a/api/v1alpha1/zz_generated.floatingip-resource.go b/api/v1alpha1/zz_generated.floatingip-resource.go index d502e9b65..5d74a7166 100644 --- a/api/v1alpha1/zz_generated.floatingip-resource.go +++ b/api/v1alpha1/zz_generated.floatingip-resource.go @@ -75,6 +75,14 @@ type FloatingIPSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type FloatingIPStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *FloatingIPResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &FloatingIP{} diff --git a/api/v1alpha1/zz_generated.group-resource.go b/api/v1alpha1/zz_generated.group-resource.go index 653bea813..cb84a30e5 100644 --- a/api/v1alpha1/zz_generated.group-resource.go +++ b/api/v1alpha1/zz_generated.group-resource.go @@ -75,6 +75,14 @@ type GroupSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type GroupStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *GroupResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Group{} diff --git a/api/v1alpha1/zz_generated.image-resource.go b/api/v1alpha1/zz_generated.image-resource.go index e9a65eff8..fb1f5633a 100644 --- a/api/v1alpha1/zz_generated.image-resource.go +++ b/api/v1alpha1/zz_generated.image-resource.go @@ -76,6 +76,14 @@ type ImageSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -114,6 +122,13 @@ type ImageStatus struct { // +optional Resource *ImageResourceStatus `json:"resource,omitempty"` + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` + ImageStatusExtra `json:",inline"` } diff --git a/api/v1alpha1/zz_generated.keypair-resource.go b/api/v1alpha1/zz_generated.keypair-resource.go index 57d13fde6..cbd363f69 100644 --- a/api/v1alpha1/zz_generated.keypair-resource.go +++ b/api/v1alpha1/zz_generated.keypair-resource.go @@ -75,6 +75,14 @@ type KeyPairSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type KeyPairStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *KeyPairResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &KeyPair{} diff --git a/api/v1alpha1/zz_generated.network-resource.go b/api/v1alpha1/zz_generated.network-resource.go index bc60852dc..17faf4f1c 100644 --- a/api/v1alpha1/zz_generated.network-resource.go +++ b/api/v1alpha1/zz_generated.network-resource.go @@ -75,6 +75,14 @@ type NetworkSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type NetworkStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *NetworkResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Network{} diff --git a/api/v1alpha1/zz_generated.port-resource.go b/api/v1alpha1/zz_generated.port-resource.go index 8b1c25ca4..4559d0860 100644 --- a/api/v1alpha1/zz_generated.port-resource.go +++ b/api/v1alpha1/zz_generated.port-resource.go @@ -75,6 +75,14 @@ type PortSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type PortStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *PortResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Port{} diff --git a/api/v1alpha1/zz_generated.project-resource.go b/api/v1alpha1/zz_generated.project-resource.go index 33fce32e2..3498d1632 100644 --- a/api/v1alpha1/zz_generated.project-resource.go +++ b/api/v1alpha1/zz_generated.project-resource.go @@ -75,6 +75,14 @@ type ProjectSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type ProjectStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *ProjectResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Project{} diff --git a/api/v1alpha1/zz_generated.role-resource.go b/api/v1alpha1/zz_generated.role-resource.go index 5891a418b..36a390527 100644 --- a/api/v1alpha1/zz_generated.role-resource.go +++ b/api/v1alpha1/zz_generated.role-resource.go @@ -75,6 +75,14 @@ type RoleSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type RoleStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *RoleResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Role{} diff --git a/api/v1alpha1/zz_generated.roleassignment-resource.go b/api/v1alpha1/zz_generated.roleassignment-resource.go index 0453da8a4..34b3277d4 100644 --- a/api/v1alpha1/zz_generated.roleassignment-resource.go +++ b/api/v1alpha1/zz_generated.roleassignment-resource.go @@ -67,6 +67,14 @@ type RoleAssignmentSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -99,6 +107,13 @@ type RoleAssignmentStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *RoleAssignmentResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &RoleAssignment{} diff --git a/api/v1alpha1/zz_generated.router-resource.go b/api/v1alpha1/zz_generated.router-resource.go index 68d83bd53..c901b07e2 100644 --- a/api/v1alpha1/zz_generated.router-resource.go +++ b/api/v1alpha1/zz_generated.router-resource.go @@ -75,6 +75,14 @@ type RouterSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type RouterStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *RouterResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Router{} diff --git a/api/v1alpha1/zz_generated.securitygroup-resource.go b/api/v1alpha1/zz_generated.securitygroup-resource.go index ac0a921a6..31192086d 100644 --- a/api/v1alpha1/zz_generated.securitygroup-resource.go +++ b/api/v1alpha1/zz_generated.securitygroup-resource.go @@ -75,6 +75,14 @@ type SecurityGroupSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type SecurityGroupStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *SecurityGroupResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &SecurityGroup{} diff --git a/api/v1alpha1/zz_generated.server-resource.go b/api/v1alpha1/zz_generated.server-resource.go index 011c5896d..401a82819 100644 --- a/api/v1alpha1/zz_generated.server-resource.go +++ b/api/v1alpha1/zz_generated.server-resource.go @@ -75,6 +75,14 @@ type ServerSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type ServerStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *ServerResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Server{} diff --git a/api/v1alpha1/zz_generated.servergroup-resource.go b/api/v1alpha1/zz_generated.servergroup-resource.go index 9f478e276..8a36d393f 100644 --- a/api/v1alpha1/zz_generated.servergroup-resource.go +++ b/api/v1alpha1/zz_generated.servergroup-resource.go @@ -75,6 +75,14 @@ type ServerGroupSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type ServerGroupStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *ServerGroupResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &ServerGroup{} diff --git a/api/v1alpha1/zz_generated.service-resource.go b/api/v1alpha1/zz_generated.service-resource.go index 0c0182818..b55800b06 100644 --- a/api/v1alpha1/zz_generated.service-resource.go +++ b/api/v1alpha1/zz_generated.service-resource.go @@ -75,6 +75,14 @@ type ServiceSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type ServiceStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *ServiceResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Service{} diff --git a/api/v1alpha1/zz_generated.sharenetwork-resource.go b/api/v1alpha1/zz_generated.sharenetwork-resource.go index 3c73d2b69..ef15d712e 100644 --- a/api/v1alpha1/zz_generated.sharenetwork-resource.go +++ b/api/v1alpha1/zz_generated.sharenetwork-resource.go @@ -75,6 +75,14 @@ type ShareNetworkSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type ShareNetworkStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *ShareNetworkResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &ShareNetwork{} diff --git a/api/v1alpha1/zz_generated.subnet-resource.go b/api/v1alpha1/zz_generated.subnet-resource.go index 0151f3064..64e115cbc 100644 --- a/api/v1alpha1/zz_generated.subnet-resource.go +++ b/api/v1alpha1/zz_generated.subnet-resource.go @@ -75,6 +75,14 @@ type SubnetSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type SubnetStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *SubnetResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Subnet{} diff --git a/api/v1alpha1/zz_generated.trunk-resource.go b/api/v1alpha1/zz_generated.trunk-resource.go index eb4c5e844..f06f78ebb 100644 --- a/api/v1alpha1/zz_generated.trunk-resource.go +++ b/api/v1alpha1/zz_generated.trunk-resource.go @@ -75,6 +75,14 @@ type TrunkSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type TrunkStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *TrunkResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Trunk{} diff --git a/api/v1alpha1/zz_generated.user-resource.go b/api/v1alpha1/zz_generated.user-resource.go index b109c1d3f..05da64833 100644 --- a/api/v1alpha1/zz_generated.user-resource.go +++ b/api/v1alpha1/zz_generated.user-resource.go @@ -75,6 +75,14 @@ type UserSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type UserStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *UserResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &User{} diff --git a/api/v1alpha1/zz_generated.volume-resource.go b/api/v1alpha1/zz_generated.volume-resource.go index 9451474fb..dbacc68fa 100644 --- a/api/v1alpha1/zz_generated.volume-resource.go +++ b/api/v1alpha1/zz_generated.volume-resource.go @@ -75,6 +75,14 @@ type VolumeSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type VolumeStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *VolumeResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &Volume{} diff --git a/api/v1alpha1/zz_generated.volumetype-resource.go b/api/v1alpha1/zz_generated.volumetype-resource.go index 5f583a2bd..fb7dd9950 100644 --- a/api/v1alpha1/zz_generated.volumetype-resource.go +++ b/api/v1alpha1/zz_generated.volumetype-resource.go @@ -75,6 +75,14 @@ type VolumeTypeSpec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -112,6 +120,13 @@ type VolumeTypeStatus struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *VolumeTypeResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } var _ ObjectWithConditions = &VolumeType{} diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 439fca722..29d2fa1b1 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -896,6 +896,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeSpec(ref c Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -908,7 +914,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeSpec(ref c }, }, 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"}, + "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", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -956,11 +962,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AddressScopeStatus(ref Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AddressScopeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -1557,6 +1569,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialS Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -1569,7 +1587,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialS }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec", "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.ApplicationCredentialImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -1617,11 +1635,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ApplicationCredentialS Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ApplicationCredentialResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -1910,6 +1934,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_DomainSpec(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -1922,7 +1952,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_DomainSpec(ref common. }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -1970,11 +2000,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_DomainStatus(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -2272,6 +2308,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointSpec(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -2284,7 +2326,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointSpec(ref commo }, }, 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"}, + "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", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -2332,11 +2374,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_EndpointStatus(ref com Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.EndpointResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -3131,6 +3179,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorSpec(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -3143,7 +3197,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorSpec(ref common. }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -3191,11 +3245,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FlavorStatus(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FlavorResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -3694,6 +3754,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref com Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -3706,7 +3772,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPSpec(ref com }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -3754,11 +3820,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_FloatingIPStatus(ref c Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FloatingIPResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -4019,6 +4091,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_GroupSpec(ref common.R Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -4031,7 +4109,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_GroupSpec(ref common.R }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -4079,11 +4157,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_GroupStatus(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -4820,6 +4904,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ImageSpec(ref common.R Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -4832,7 +4922,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ImageSpec(ref common.R }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -4880,6 +4970,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatus(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, "downloadAttempts": { SchemaProps: spec.SchemaProps{ Description: "downloadAttempts is the number of times the controller has attempted to download the image contents", @@ -4891,7 +4987,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ImageStatus(ref common }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ImageResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -5172,6 +5268,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -5184,7 +5286,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairSpec(ref common }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -5232,11 +5334,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_KeyPairStatus(ref comm Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.KeyPairResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -5828,6 +5936,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -5840,7 +5954,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref common }, }, 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.NetworkImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceSpec"}, + "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.NetworkImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -5888,11 +6002,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_NetworkStatus(ref comm Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -6684,6 +6804,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref common.Re Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -6696,7 +6822,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortSpec(ref common.Re }, }, 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.PortImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceSpec"}, + "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.PortImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -6744,11 +6870,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortStatus(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -7170,6 +7302,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -7182,7 +7320,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProjectSpec(ref common }, }, 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.ProjectImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceSpec"}, + "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.ProjectImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -7230,11 +7368,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref comm Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -7622,6 +7766,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentSpec(ref Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -7634,7 +7784,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentSpec(ref }, }, 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.RoleAssignmentImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec"}, + "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.RoleAssignmentImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -7675,11 +7825,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentStatus(r Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -7889,6 +8045,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref common.Re Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -7901,7 +8063,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleSpec(ref common.Re }, }, 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.RoleImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec"}, + "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.RoleImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -7949,11 +8111,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RoleStatus(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -8279,10 +8447,18 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceSpec(re Format: "", }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, }, Required: []string{"type", "routerRef"}, }, }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -8323,11 +8499,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RouterInterfaceStatus( Format: "", }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation of the resource.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -8630,6 +8812,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RouterSpec(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -8642,7 +8830,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RouterSpec(ref common. }, }, 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.RouterImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceSpec"}, + "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.RouterImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -8690,11 +8878,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_RouterStatus(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RouterResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -9283,6 +9477,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -9295,7 +9495,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref }, }, 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.SecurityGroupImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceSpec"}, + "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.SecurityGroupImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -9343,11 +9543,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(re Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -9842,6 +10048,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupSpec(ref co Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -9854,7 +10066,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupSpec(ref co }, }, 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.ServerGroupImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceSpec"}, + "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.ServerGroupImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -9902,11 +10114,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupStatus(ref Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -10606,6 +10824,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -10618,7 +10842,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref common. }, }, 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.ServerImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceSpec"}, + "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.ServerImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -10666,11 +10890,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -10992,6 +11222,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -11004,7 +11240,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServiceSpec(ref common }, }, 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.ServiceImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceSpec"}, + "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.ServiceImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -11052,11 +11288,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServiceStatus(ref comm Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServiceResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -11381,6 +11623,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkSpec(ref c Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -11393,7 +11641,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkSpec(ref c }, }, 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.ShareNetworkImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceSpec"}, + "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.ShareNetworkImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -11441,11 +11689,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ShareNetworkStatus(ref Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ShareNetworkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -12153,6 +12407,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -12165,7 +12425,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetSpec(ref common. }, }, 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.SubnetImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec"}, + "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.SubnetImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -12213,11 +12473,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_SubnetStatus(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SubnetResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -12723,6 +12989,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSpec(ref common.R Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -12735,7 +13007,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkSpec(ref common.R }, }, 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"}, + "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", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -12783,11 +13055,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_TrunkStatus(ref common Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.TrunkResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -13185,6 +13463,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserSpec(ref common.Re Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -13197,7 +13481,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserSpec(ref common.Re }, }, 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"}, + "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", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -13245,11 +13529,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserStatus(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -13823,6 +14113,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeSpec(ref common. Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -13835,7 +14131,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeSpec(ref common. }, }, 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.VolumeImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceSpec"}, + "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.VolumeImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -13883,11 +14179,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeStatus(ref commo Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -14252,6 +14554,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeSpec(ref com Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), }, }, + "resyncPeriod": { + SchemaProps: spec.SchemaProps{ + Description: "resyncPeriod defines how frequently the controller will re-reconcile this resource even when no changes have been detected. This overrides the global default resync period. The value must be a valid Go duration string, e.g. \"10m\", \"1h\". Set to \"0s\" to disable periodic resync for this resource. Very low values may cause excessive OpenStack API load.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, "cloudCredentialsRef": { SchemaProps: spec.SchemaProps{ Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", @@ -14264,7 +14572,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeSpec(ref com }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceSpec"}, + "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.VolumeTypeImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -14312,11 +14620,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeTypeStatus(ref c Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus"), }, }, + "lastSyncTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastSyncTime is the timestamp of the last successful reconciliation that fetched state from OpenStack. It is updated each time the controller successfully reads the resource state from the OpenStack API.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.VolumeTypeResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/cmd/resource-generator/data/adapter.template b/cmd/resource-generator/data/adapter.template index dca45ba90..6e9c61023 100644 --- a/cmd/resource-generator/data/adapter.template +++ b/cmd/resource-generator/data/adapter.template @@ -17,6 +17,8 @@ limitations under the License. package {{ .NameLower }} import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -54,6 +56,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { {{- if .NoResourceID }} return nil diff --git a/cmd/resource-generator/data/api.template b/cmd/resource-generator/data/api.template index 04e5e0bed..6018a9aa5 100644 --- a/cmd/resource-generator/data/api.template +++ b/cmd/resource-generator/data/api.template @@ -94,6 +94,14 @@ type {{ .Name }}Spec struct { // +optional ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + // resyncPeriod defines how frequently the controller will re-reconcile + // this resource even when no changes have been detected. This overrides + // the global default resync period. The value must be a valid Go duration + // string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + // this resource. Very low values may cause excessive OpenStack API load. + // +optional + ResyncPeriod *metav1.Duration `json:"resyncPeriod,omitempty"` //nolint:kubeapilinter // metav1.Duration is appropriate for user-facing duration config + // cloudCredentialsRef points to a secret containing OpenStack credentials // +required CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` @@ -133,6 +141,13 @@ type {{ .Name }}Status struct { // resource contains the observed state of the OpenStack resource. // +optional Resource *{{ .Name }}ResourceStatus `json:"resource,omitempty"` + + // lastSyncTime is the timestamp of the last successful reconciliation + // that fetched state from OpenStack. It is updated each time the + // controller successfully reads the resource state from the OpenStack + // API. + // +optional + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` {{- if .StatusExtraType }} {{ .StatusExtraType }} `json:",inline"` diff --git a/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml b/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml index a63c83ef2..1416386f3 100644 --- a/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_addressscopes.yaml @@ -204,6 +204,14 @@ spec: required: - ipVersion type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -303,6 +311,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml index c3d4c5dce..34414c26c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_applicationcredentials.yaml @@ -251,6 +251,14 @@ spec: x-kubernetes-validations: - message: ApplicationCredentialResourceSpec is immutable rule: self == oldSelf + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -350,6 +358,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_domains.yaml b/config/crd/bases/openstack.k-orc.cloud_domains.yaml index 9c9fc2c82..7f74e8a68 100644 --- a/config/crd/bases/openstack.k-orc.cloud_domains.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_domains.yaml @@ -168,6 +168,14 @@ spec: minLength: 1 type: string type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -267,6 +275,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml index efddd5c20..8f753813f 100644 --- a/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_endpoints.yaml @@ -194,6 +194,14 @@ spec: - serviceRef - url type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -293,6 +301,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml index a86ac5630..089c78ea6 100644 --- a/config/crd/bases/openstack.k-orc.cloud_flavors.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_flavors.yaml @@ -281,6 +281,14 @@ spec: - ram - vcpus type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -380,6 +388,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml index 51574f4f1..041faf837 100644 --- a/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_floatingips.yaml @@ -313,6 +313,14 @@ spec: - message: Exactly one of 'floatingNetworkRef' or 'floatingSubnetRef' must be set rule: has(self.floatingNetworkRef) != has(self.floatingSubnetRef) + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -412,6 +420,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_groups.yaml b/config/crd/bases/openstack.k-orc.cloud_groups.yaml index 9418c3ded..49e5ad809 100644 --- a/config/crd/bases/openstack.k-orc.cloud_groups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_groups.yaml @@ -173,6 +173,14 @@ spec: minLength: 1 type: string type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -272,6 +280,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_images.yaml b/config/crd/bases/openstack.k-orc.cloud_images.yaml index 39cfc79ad..ad3ebfe3e 100644 --- a/config/crd/bases/openstack.k-orc.cloud_images.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_images.yaml @@ -541,6 +541,14 @@ spec: - community type: string type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -647,6 +655,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml index c02878ff7..051ec07e8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_keypairs.yaml @@ -169,6 +169,14 @@ spec: required: - publicKey type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -268,6 +276,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_networks.yaml b/config/crd/bases/openstack.k-orc.cloud_networks.yaml index ac5f02b8b..9d5fd6544 100644 --- a/config/crd/bases/openstack.k-orc.cloud_networks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_networks.yaml @@ -310,6 +310,14 @@ spec: type: array x-kubernetes-list-type: set type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -409,6 +417,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index 8212ec147..6f822daff 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -477,6 +477,14 @@ spec: set to Disabled rule: 'has(self.portSecurity) && self.portSecurity == ''Disabled'' ? !has(self.allowedAddressPairs) : true' + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -576,6 +584,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_projects.yaml b/config/crd/bases/openstack.k-orc.cloud_projects.yaml index e673b6217..f2ac9df09 100644 --- a/config/crd/bases/openstack.k-orc.cloud_projects.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_projects.yaml @@ -233,6 +233,14 @@ spec: type: array x-kubernetes-list-type: set type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -332,6 +340,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml index 3668ad03e..7902807d1 100644 --- a/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml @@ -197,6 +197,14 @@ spec: && has(self.domainRef)) - message: RoleAssignmentResourceSpec is immutable rule: self == oldSelf + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -292,6 +300,14 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_roles.yaml b/config/crd/bases/openstack.k-orc.cloud_roles.yaml index 4ec04bfa5..12fbcd4dc 100644 --- a/config/crd/bases/openstack.k-orc.cloud_roles.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_roles.yaml @@ -173,6 +173,14 @@ spec: minLength: 1 type: string type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -272,6 +280,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml b/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml index bbb4187b8..3fde7b424 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routerinterfaces.yaml @@ -50,6 +50,14 @@ spec: spec: description: spec specifies the desired state of the resource. properties: + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string routerRef: description: routerRef references the router to which this interface belongs. @@ -77,8 +85,13 @@ spec: - message: subnetRef is required when type is 'Subnet' and not permitted otherwise rule: 'self.type == ''Subnet'' ? has(self.subnetRef) : !has(self.subnetRef)' - - message: RouterInterfaceResourceSpec is immutable - rule: self == oldSelf + - message: type is immutable + rule: self.type == oldSelf.type + - message: routerRef is immutable + rule: self.routerRef == oldSelf.routerRef + - message: subnetRef is immutable + rule: has(self.subnetRef) == has(oldSelf.subnetRef) && (!has(self.subnetRef) + || self.subnetRef == oldSelf.subnetRef) status: description: status defines the observed state of the resource. properties: @@ -161,6 +174,12 @@ spec: router interface maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + of the resource. + format: date-time + type: string type: object required: - spec diff --git a/config/crd/bases/openstack.k-orc.cloud_routers.yaml b/config/crd/bases/openstack.k-orc.cloud_routers.yaml index 7dade737e..520d0b258 100644 --- a/config/crd/bases/openstack.k-orc.cloud_routers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_routers.yaml @@ -300,6 +300,14 @@ spec: type: array x-kubernetes-list-type: set type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -399,6 +407,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml index 19eb7d8f9..31ecafd59 100644 --- a/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_securitygroups.yaml @@ -375,6 +375,14 @@ spec: type: array x-kubernetes-list-type: set type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -474,6 +482,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml index c0bdb3ced..1bd83c87a 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servergroups.yaml @@ -181,6 +181,14 @@ spec: policy rule: 'has(self.rules) && self.rules.maxServerPerHost > 0 ? self.policy == ''anti-affinity'' : true' + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -280,6 +288,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml index 62aba055f..19563cd1c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -448,6 +448,14 @@ spec: rule: has(self.imageRef) || has(self.bootVolume) - message: imageRef and bootVolume are mutually exclusive rule: '!(has(self.imageRef) && has(self.bootVolume))' + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -547,6 +555,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_services.yaml b/config/crd/bases/openstack.k-orc.cloud_services.yaml index 8c5f96c75..3b6b4ac25 100644 --- a/config/crd/bases/openstack.k-orc.cloud_services.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_services.yaml @@ -177,6 +177,14 @@ spec: required: - type type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -276,6 +284,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml b/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml index bd42cf06c..907ab70f9 100644 --- a/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_sharenetworks.yaml @@ -186,6 +186,14 @@ spec: x-kubernetes-validations: - message: networkRef and subnetRef must be specified together rule: has(self.networkRef) == has(self.subnetRef) + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -285,6 +293,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml index 5ff2ede1a..0a6e97abc 100644 --- a/config/crd/bases/openstack.k-orc.cloud_subnets.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_subnets.yaml @@ -466,6 +466,14 @@ spec: - ipVersion - networkRef type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -565,6 +573,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml index aefa17223..da9407964 100644 --- a/config/crd/bases/openstack.k-orc.cloud_trunks.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_trunks.yaml @@ -312,6 +312,14 @@ spec: required: - portRef type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -411,6 +419,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml index e9dc0fa8c..7f872c6d2 100644 --- a/config/crd/bases/openstack.k-orc.cloud_users.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml @@ -198,6 +198,14 @@ spec: x-kubernetes-validations: - message: passwordRef may not be removed once set rule: '!has(oldSelf.passwordRef) || has(self.passwordRef)' + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -297,6 +305,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml index 500dec639..a740ea21e 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml @@ -238,6 +238,14 @@ spec: required: - size type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -337,6 +345,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml index 384a5f215..1ba9b060f 100644 --- a/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_volumetypes.yaml @@ -192,6 +192,14 @@ spec: pattern: ^[^,]+$ type: string type: object + resyncPeriod: + description: |- + resyncPeriod defines how frequently the controller will re-reconcile + this resource even when no changes have been detected. This overrides + the global default resync period. The value must be a valid Go duration + string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for + this resource. Very low values may cause excessive OpenStack API load. + type: string required: - cloudCredentialsRef type: object @@ -291,6 +299,14 @@ spec: description: id is the unique identifier of the OpenStack resource. maxLength: 1024 type: string + lastSyncTime: + description: |- + lastSyncTime is the timestamp of the last successful reconciliation + that fetched state from OpenStack. It is updated each time the + controller successfully reads the resource state from the OpenStack + API. + format: date-time + type: string resource: description: resource contains the observed state of the OpenStack resource. diff --git a/internal/controllers/addressscope/zz_generated.adapter.go b/internal/controllers/addressscope/zz_generated.adapter.go index 768861dbd..5fb17a74d 100644 --- a/internal/controllers/addressscope/zz_generated.adapter.go +++ b/internal/controllers/addressscope/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package addressscope import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/applicationcredential/zz_generated.adapter.go b/internal/controllers/applicationcredential/zz_generated.adapter.go index 55f0b5346..d4f726aeb 100644 --- a/internal/controllers/applicationcredential/zz_generated.adapter.go +++ b/internal/controllers/applicationcredential/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package applicationcredential import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/domain/zz_generated.adapter.go b/internal/controllers/domain/zz_generated.adapter.go index 6a386af72..0a8c3b6ef 100644 --- a/internal/controllers/domain/zz_generated.adapter.go +++ b/internal/controllers/domain/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package domain import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/endpoint/zz_generated.adapter.go b/internal/controllers/endpoint/zz_generated.adapter.go index fe95ab43f..b5b462573 100644 --- a/internal/controllers/endpoint/zz_generated.adapter.go +++ b/internal/controllers/endpoint/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package endpoint import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/flavor/zz_generated.adapter.go b/internal/controllers/flavor/zz_generated.adapter.go index 936fc6735..49af59c95 100644 --- a/internal/controllers/flavor/zz_generated.adapter.go +++ b/internal/controllers/flavor/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package flavor import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/floatingip/zz_generated.adapter.go b/internal/controllers/floatingip/zz_generated.adapter.go index c0ff372fb..018137366 100644 --- a/internal/controllers/floatingip/zz_generated.adapter.go +++ b/internal/controllers/floatingip/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package floatingip import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/generic/interfaces/adapter.go b/internal/controllers/generic/interfaces/adapter.go index 319e19c10..932d9ca3f 100644 --- a/internal/controllers/generic/interfaces/adapter.go +++ b/internal/controllers/generic/interfaces/adapter.go @@ -33,6 +33,8 @@ type APIObjectAdapter[orcObjectPT any, resourceSpecT any, filterT any] interface GetManagementPolicy() orcv1alpha1.ManagementPolicy GetManagedOptions() *orcv1alpha1.ManagedOptions + GetResyncPeriod() *metav1.Duration + GetLastSyncTime() *metav1.Time GetStatusID() *string GetResourceSpec() *resourceSpecT diff --git a/internal/controllers/group/zz_generated.adapter.go b/internal/controllers/group/zz_generated.adapter.go index be06e584f..10748fe6d 100644 --- a/internal/controllers/group/zz_generated.adapter.go +++ b/internal/controllers/group/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package group import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/image/zz_generated.adapter.go b/internal/controllers/image/zz_generated.adapter.go index fe096571b..5fa2b7639 100644 --- a/internal/controllers/image/zz_generated.adapter.go +++ b/internal/controllers/image/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package image import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/keypair/zz_generated.adapter.go b/internal/controllers/keypair/zz_generated.adapter.go index d3b72644c..0cb2ae1a5 100644 --- a/internal/controllers/keypair/zz_generated.adapter.go +++ b/internal/controllers/keypair/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package keypair import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/network/zz_generated.adapter.go b/internal/controllers/network/zz_generated.adapter.go index 771518735..59df47ad3 100644 --- a/internal/controllers/network/zz_generated.adapter.go +++ b/internal/controllers/network/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package network import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/port/zz_generated.adapter.go b/internal/controllers/port/zz_generated.adapter.go index 1cafbd343..4862fbb04 100644 --- a/internal/controllers/port/zz_generated.adapter.go +++ b/internal/controllers/port/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package port import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/project/zz_generated.adapter.go b/internal/controllers/project/zz_generated.adapter.go index fea8a21c1..f88a3bc8b 100644 --- a/internal/controllers/project/zz_generated.adapter.go +++ b/internal/controllers/project/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package project import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/role/zz_generated.adapter.go b/internal/controllers/role/zz_generated.adapter.go index 5587b85d4..b87c8d338 100644 --- a/internal/controllers/role/zz_generated.adapter.go +++ b/internal/controllers/role/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package role import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/roleassignment/zz_generated.adapter.go b/internal/controllers/roleassignment/zz_generated.adapter.go index 9941f472b..4247adea2 100644 --- a/internal/controllers/roleassignment/zz_generated.adapter.go +++ b/internal/controllers/roleassignment/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package roleassignment import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return nil } diff --git a/internal/controllers/router/zz_generated.adapter.go b/internal/controllers/router/zz_generated.adapter.go index ccab08587..fedcdd75a 100644 --- a/internal/controllers/router/zz_generated.adapter.go +++ b/internal/controllers/router/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package router import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/securitygroup/zz_generated.adapter.go b/internal/controllers/securitygroup/zz_generated.adapter.go index 1b055740c..fda940c01 100644 --- a/internal/controllers/securitygroup/zz_generated.adapter.go +++ b/internal/controllers/securitygroup/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package securitygroup import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/server/zz_generated.adapter.go b/internal/controllers/server/zz_generated.adapter.go index 1b51cde39..a18d78c8e 100644 --- a/internal/controllers/server/zz_generated.adapter.go +++ b/internal/controllers/server/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package server import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/servergroup/zz_generated.adapter.go b/internal/controllers/servergroup/zz_generated.adapter.go index dc272f462..2fc71f170 100644 --- a/internal/controllers/servergroup/zz_generated.adapter.go +++ b/internal/controllers/servergroup/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package servergroup import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/service/zz_generated.adapter.go b/internal/controllers/service/zz_generated.adapter.go index f70ba04d9..719f23b25 100644 --- a/internal/controllers/service/zz_generated.adapter.go +++ b/internal/controllers/service/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package service import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/sharenetwork/zz_generated.adapter.go b/internal/controllers/sharenetwork/zz_generated.adapter.go index b89627a5b..a81ac97d9 100644 --- a/internal/controllers/sharenetwork/zz_generated.adapter.go +++ b/internal/controllers/sharenetwork/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package sharenetwork import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/subnet/zz_generated.adapter.go b/internal/controllers/subnet/zz_generated.adapter.go index 34c84d5b8..dedcdcf0f 100644 --- a/internal/controllers/subnet/zz_generated.adapter.go +++ b/internal/controllers/subnet/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package subnet import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/trunk/zz_generated.adapter.go b/internal/controllers/trunk/zz_generated.adapter.go index ef7e54457..274c087bf 100644 --- a/internal/controllers/trunk/zz_generated.adapter.go +++ b/internal/controllers/trunk/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package trunk import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/user/zz_generated.adapter.go b/internal/controllers/user/zz_generated.adapter.go index 718a1ef46..fd800ac39 100644 --- a/internal/controllers/user/zz_generated.adapter.go +++ b/internal/controllers/user/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package user import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/volume/zz_generated.adapter.go b/internal/controllers/volume/zz_generated.adapter.go index 956b64693..6acc67b2b 100644 --- a/internal/controllers/volume/zz_generated.adapter.go +++ b/internal/controllers/volume/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package volume import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/internal/controllers/volumetype/zz_generated.adapter.go b/internal/controllers/volumetype/zz_generated.adapter.go index 9f19fa751..ac8f117d5 100644 --- a/internal/controllers/volumetype/zz_generated.adapter.go +++ b/internal/controllers/volumetype/zz_generated.adapter.go @@ -18,6 +18,8 @@ limitations under the License. package volumetype import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" ) @@ -55,6 +57,14 @@ func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { return f.Spec.ManagedOptions } +func (f adapterT) GetResyncPeriod() *metav1.Duration { + return f.Spec.ResyncPeriod +} + +func (f adapterT) GetLastSyncTime() *metav1.Time { + return f.Status.LastSyncTime +} + func (f adapterT) GetStatusID() *string { return f.Status.ID } diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go index 4a42ce57c..1ef5b5cee 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopespec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // AddressScopeSpecApplyConfiguration represents a declarative configuration of the AddressScopeSpec type for use @@ -29,6 +30,7 @@ type AddressScopeSpecApplyConfiguration struct { Resource *AddressScopeResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *AddressScopeSpecApplyConfiguration) WithManagedOptions(value *ManagedOp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *AddressScopeSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *AddressScopeSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go index c2d823af0..b7ecb594f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/addressscopestatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // 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"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *AddressScopeResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // AddressScopeStatusApplyConfiguration constructs a declarative configuration of the AddressScopeStatus type for use with @@ -64,3 +66,11 @@ func (b *AddressScopeStatusApplyConfiguration) WithResource(value *AddressScopeR b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *AddressScopeStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *AddressScopeStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go index d73e0886b..09f1178b1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ApplicationCredentialSpecApplyConfiguration represents a declarative configuration of the ApplicationCredentialSpec type for use @@ -29,6 +30,7 @@ type ApplicationCredentialSpecApplyConfiguration struct { Resource *ApplicationCredentialResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ApplicationCredentialSpecApplyConfiguration) WithManagedOptions(value * return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ApplicationCredentialSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ApplicationCredentialSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go index 2dc54e77a..70e271a57 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/applicationcredentialstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // ApplicationCredentialStatusApplyConfiguration represents a declarative configuration of the ApplicationCredentialStatus type for use // with apply. type ApplicationCredentialStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *ApplicationCredentialResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ApplicationCredentialResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // ApplicationCredentialStatusApplyConfiguration constructs a declarative configuration of the ApplicationCredentialStatus type for use with @@ -64,3 +66,11 @@ func (b *ApplicationCredentialStatusApplyConfiguration) WithResource(value *Appl b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ApplicationCredentialStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ApplicationCredentialStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go index e5357a87e..dc2da0444 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DomainSpecApplyConfiguration represents a declarative configuration of the DomainSpec type for use @@ -29,6 +30,7 @@ type DomainSpecApplyConfiguration struct { Resource *DomainResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *DomainSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsA return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *DomainSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *DomainSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go index c7540a168..c87fc50e1 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/domainstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // DomainStatusApplyConfiguration represents a declarative configuration of the DomainStatus type for use // with apply. type DomainStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *DomainResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *DomainResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // DomainStatusApplyConfiguration constructs a declarative configuration of the DomainStatus type for use with @@ -64,3 +66,11 @@ func (b *DomainStatusApplyConfiguration) WithResource(value *DomainResourceStatu b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *DomainStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *DomainStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go index 198237c30..ddde864fe 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EndpointSpecApplyConfiguration represents a declarative configuration of the EndpointSpec type for use @@ -29,6 +30,7 @@ type EndpointSpecApplyConfiguration struct { Resource *EndpointResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *EndpointSpecApplyConfiguration) WithManagedOptions(value *ManagedOption return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *EndpointSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *EndpointSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go index d620075a5..63156d676 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/endpointstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // 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"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *EndpointResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // EndpointStatusApplyConfiguration constructs a declarative configuration of the EndpointStatus type for use with @@ -64,3 +66,11 @@ func (b *EndpointStatusApplyConfiguration) WithResource(value *EndpointResourceS b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *EndpointStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *EndpointStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go index abe7c0d07..28f5e9f50 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // FlavorSpecApplyConfiguration represents a declarative configuration of the FlavorSpec type for use @@ -29,6 +30,7 @@ type FlavorSpecApplyConfiguration struct { Resource *FlavorResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *FlavorSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsA return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *FlavorSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *FlavorSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go index 928a60e26..dd370aa1b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/flavorstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // FlavorStatusApplyConfiguration represents a declarative configuration of the FlavorStatus type for use // with apply. type FlavorStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *FlavorResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *FlavorResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // FlavorStatusApplyConfiguration constructs a declarative configuration of the FlavorStatus type for use with @@ -64,3 +66,11 @@ func (b *FlavorStatusApplyConfiguration) WithResource(value *FlavorResourceStatu b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *FlavorStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *FlavorStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go index 8fe12ac3a..066b710b8 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // FloatingIPSpecApplyConfiguration represents a declarative configuration of the FloatingIPSpec type for use @@ -29,6 +30,7 @@ type FloatingIPSpecApplyConfiguration struct { Resource *FloatingIPResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *FloatingIPSpecApplyConfiguration) WithManagedOptions(value *ManagedOpti return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *FloatingIPSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *FloatingIPSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go index 61291bdd7..eb856b839 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/floatingipstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // FloatingIPStatusApplyConfiguration represents a declarative configuration of the FloatingIPStatus type for use // with apply. type FloatingIPStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *FloatingIPResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *FloatingIPResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // FloatingIPStatusApplyConfiguration constructs a declarative configuration of the FloatingIPStatus type for use with @@ -64,3 +66,11 @@ func (b *FloatingIPStatusApplyConfiguration) WithResource(value *FloatingIPResou b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *FloatingIPStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *FloatingIPStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go index 59a744101..218db914f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // GroupSpecApplyConfiguration represents a declarative configuration of the GroupSpec type for use @@ -29,6 +30,7 @@ type GroupSpecApplyConfiguration struct { Resource *GroupResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *GroupSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsAp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *GroupSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *GroupSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go index 564e9cdc4..c7f97fef2 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/groupstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // GroupStatusApplyConfiguration represents a declarative configuration of the GroupStatus type for use // with apply. type GroupStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *GroupResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *GroupResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // GroupStatusApplyConfiguration constructs a declarative configuration of the GroupStatus type for use with @@ -64,3 +66,11 @@ func (b *GroupStatusApplyConfiguration) WithResource(value *GroupResourceStatusA b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *GroupStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *GroupStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go index 7982dcda4..e63cfdbce 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagespec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ImageSpecApplyConfiguration represents a declarative configuration of the ImageSpec type for use @@ -29,6 +30,7 @@ type ImageSpecApplyConfiguration struct { Resource *ImageResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ImageSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsAp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ImageSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ImageSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go index 033e2cd30..0a4ba2519 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/imagestatus.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) @@ -28,6 +29,7 @@ type ImageStatusApplyConfiguration struct { Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` ID *string `json:"id,omitempty"` Resource *ImageResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` ImageStatusExtraApplyConfiguration `json:",inline"` } @@ -66,6 +68,14 @@ func (b *ImageStatusApplyConfiguration) WithResource(value *ImageResourceStatusA return b } +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ImageStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ImageStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} + // WithDownloadAttempts sets the DownloadAttempts field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the DownloadAttempts field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go index 725c5278d..e8c3e5e21 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // KeyPairSpecApplyConfiguration represents a declarative configuration of the KeyPairSpec type for use @@ -29,6 +30,7 @@ type KeyPairSpecApplyConfiguration struct { Resource *KeyPairResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *KeyPairSpecApplyConfiguration) WithManagedOptions(value *ManagedOptions return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *KeyPairSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *KeyPairSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go index fb316a629..591a9a77b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/keypairstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // KeyPairStatusApplyConfiguration represents a declarative configuration of the KeyPairStatus type for use // with apply. type KeyPairStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *KeyPairResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *KeyPairResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // KeyPairStatusApplyConfiguration constructs a declarative configuration of the KeyPairStatus type for use with @@ -64,3 +66,11 @@ func (b *KeyPairStatusApplyConfiguration) WithResource(value *KeyPairResourceSta b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *KeyPairStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *KeyPairStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go index a27a7b21c..682a2b264 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // NetworkSpecApplyConfiguration represents a declarative configuration of the NetworkSpec type for use @@ -29,6 +30,7 @@ type NetworkSpecApplyConfiguration struct { Resource *NetworkResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *NetworkSpecApplyConfiguration) WithManagedOptions(value *ManagedOptions return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *NetworkSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *NetworkSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go index 1d671bd04..fa0fa85ec 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/networkstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // NetworkStatusApplyConfiguration represents a declarative configuration of the NetworkStatus type for use // with apply. type NetworkStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *NetworkResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *NetworkResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // NetworkStatusApplyConfiguration constructs a declarative configuration of the NetworkStatus type for use with @@ -64,3 +66,11 @@ func (b *NetworkStatusApplyConfiguration) WithResource(value *NetworkResourceSta b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *NetworkStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *NetworkStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go index f3a31f9d1..15f08e7b0 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // PortSpecApplyConfiguration represents a declarative configuration of the PortSpec type for use @@ -29,6 +30,7 @@ type PortSpecApplyConfiguration struct { Resource *PortResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *PortSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *PortSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *PortSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go index f902f1538..7fe027d83 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // PortStatusApplyConfiguration represents a declarative configuration of the PortStatus type for use // with apply. type PortStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *PortResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *PortResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // PortStatusApplyConfiguration constructs a declarative configuration of the PortStatus type for use with @@ -64,3 +66,11 @@ func (b *PortStatusApplyConfiguration) WithResource(value *PortResourceStatusApp b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *PortStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *PortStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go index fe81e80ba..b9a681c7d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ProjectSpecApplyConfiguration represents a declarative configuration of the ProjectSpec type for use @@ -29,6 +30,7 @@ type ProjectSpecApplyConfiguration struct { Resource *ProjectResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ProjectSpecApplyConfiguration) WithManagedOptions(value *ManagedOptions return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ProjectSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ProjectSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go index 328980bcf..469f29ea4 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/projectstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // ProjectStatusApplyConfiguration represents a declarative configuration of the ProjectStatus type for use // with apply. type ProjectStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *ProjectResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ProjectResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // ProjectStatusApplyConfiguration constructs a declarative configuration of the ProjectStatus type for use with @@ -64,3 +66,11 @@ func (b *ProjectStatusApplyConfiguration) WithResource(value *ProjectResourceSta b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ProjectStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ProjectStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go index df29d44b1..6ecab3bac 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // RoleAssignmentSpecApplyConfiguration represents a declarative configuration of the RoleAssignmentSpec type for use @@ -29,6 +30,7 @@ type RoleAssignmentSpecApplyConfiguration struct { Resource *RoleAssignmentResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *RoleAssignmentSpecApplyConfiguration) WithManagedOptions(value *Managed return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *RoleAssignmentSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *RoleAssignmentSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go index bf5edaa54..08b7d742a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go @@ -19,14 +19,16 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // RoleAssignmentStatusApplyConfiguration represents a declarative configuration of the RoleAssignmentStatus type for use // with apply. type RoleAssignmentStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - Resource *RoleAssignmentResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + Resource *RoleAssignmentResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // RoleAssignmentStatusApplyConfiguration constructs a declarative configuration of the RoleAssignmentStatus type for use with @@ -55,3 +57,11 @@ func (b *RoleAssignmentStatusApplyConfiguration) WithResource(value *RoleAssignm b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *RoleAssignmentStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *RoleAssignmentStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go index 05205d08b..bc26455d9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolespec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // RoleSpecApplyConfiguration represents a declarative configuration of the RoleSpec type for use @@ -29,6 +30,7 @@ type RoleSpecApplyConfiguration struct { Resource *RoleResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *RoleSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *RoleSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *RoleSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go index 8731d9e8b..8a2d976a6 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/rolestatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // RoleStatusApplyConfiguration represents a declarative configuration of the RoleStatus type for use // with apply. type RoleStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *RoleResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *RoleResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // RoleStatusApplyConfiguration constructs a declarative configuration of the RoleStatus type for use with @@ -64,3 +66,11 @@ func (b *RoleStatusApplyConfiguration) WithResource(value *RoleResourceStatusApp b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *RoleStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *RoleStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go index 4fccc28c6..260239a8f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacespec.go @@ -20,14 +20,16 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // RouterInterfaceSpecApplyConfiguration represents a declarative configuration of the RouterInterfaceSpec type for use // with apply. type RouterInterfaceSpecApplyConfiguration struct { - Type *apiv1alpha1.RouterInterfaceType `json:"type,omitempty"` - RouterRef *apiv1alpha1.KubernetesNameRef `json:"routerRef,omitempty"` - SubnetRef *apiv1alpha1.KubernetesNameRef `json:"subnetRef,omitempty"` + Type *apiv1alpha1.RouterInterfaceType `json:"type,omitempty"` + RouterRef *apiv1alpha1.KubernetesNameRef `json:"routerRef,omitempty"` + SubnetRef *apiv1alpha1.KubernetesNameRef `json:"subnetRef,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` } // RouterInterfaceSpecApplyConfiguration constructs a declarative configuration of the RouterInterfaceSpec type for use with @@ -59,3 +61,11 @@ func (b *RouterInterfaceSpecApplyConfiguration) WithSubnetRef(value apiv1alpha1. b.SubnetRef = &value return b } + +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *RouterInterfaceSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *RouterInterfaceSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go index 280668da5..fc9c92787 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerinterfacestatus.go @@ -19,14 +19,16 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // RouterInterfaceStatusApplyConfiguration represents a declarative configuration of the RouterInterfaceStatus type for use // with apply. type RouterInterfaceStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // RouterInterfaceStatusApplyConfiguration constructs a declarative configuration of the RouterInterfaceStatus type for use with @@ -55,3 +57,11 @@ func (b *RouterInterfaceStatusApplyConfiguration) WithID(value string) *RouterIn b.ID = &value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *RouterInterfaceStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *RouterInterfaceStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go index fb3da1400..c1dde96b9 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // RouterSpecApplyConfiguration represents a declarative configuration of the RouterSpec type for use @@ -29,6 +30,7 @@ type RouterSpecApplyConfiguration struct { Resource *RouterResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *RouterSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsA return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *RouterSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *RouterSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go index 2b42ab43e..4652956fd 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/routerstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // RouterStatusApplyConfiguration represents a declarative configuration of the RouterStatus type for use // with apply. type RouterStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *RouterResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *RouterResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // RouterStatusApplyConfiguration constructs a declarative configuration of the RouterStatus type for use with @@ -64,3 +66,11 @@ func (b *RouterStatusApplyConfiguration) WithResource(value *RouterResourceStatu b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *RouterStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *RouterStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go index aea02dbb7..51a5f5a1f 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // SecurityGroupSpecApplyConfiguration represents a declarative configuration of the SecurityGroupSpec type for use @@ -29,6 +30,7 @@ type SecurityGroupSpecApplyConfiguration struct { Resource *SecurityGroupResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *SecurityGroupSpecApplyConfiguration) WithManagedOptions(value *ManagedO return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *SecurityGroupSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *SecurityGroupSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go index 8ff720652..9237c1699 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/securitygroupstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // SecurityGroupStatusApplyConfiguration represents a declarative configuration of the SecurityGroupStatus type for use // with apply. type SecurityGroupStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *SecurityGroupResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *SecurityGroupResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // SecurityGroupStatusApplyConfiguration constructs a declarative configuration of the SecurityGroupStatus type for use with @@ -64,3 +66,11 @@ func (b *SecurityGroupStatusApplyConfiguration) WithResource(value *SecurityGrou b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *SecurityGroupStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *SecurityGroupStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go index 21efdd462..ed08d4bc3 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ServerGroupSpecApplyConfiguration represents a declarative configuration of the ServerGroupSpec type for use @@ -29,6 +30,7 @@ type ServerGroupSpecApplyConfiguration struct { Resource *ServerGroupResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ServerGroupSpecApplyConfiguration) WithManagedOptions(value *ManagedOpt return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ServerGroupSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ServerGroupSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go index 8b7e8ce34..77ba8454b 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servergroupstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // ServerGroupStatusApplyConfiguration represents a declarative configuration of the ServerGroupStatus type for use // with apply. type ServerGroupStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *ServerGroupResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ServerGroupResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // ServerGroupStatusApplyConfiguration constructs a declarative configuration of the ServerGroupStatus type for use with @@ -64,3 +66,11 @@ func (b *ServerGroupStatusApplyConfiguration) WithResource(value *ServerGroupRes b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ServerGroupStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ServerGroupStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go index 2e284079c..95b7b111d 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ServerSpecApplyConfiguration represents a declarative configuration of the ServerSpec type for use @@ -29,6 +30,7 @@ type ServerSpecApplyConfiguration struct { Resource *ServerResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ServerSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsA return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ServerSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ServerSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go index c433aafb2..43e471a4a 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // ServerStatusApplyConfiguration represents a declarative configuration of the ServerStatus type for use // with apply. type ServerStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *ServerResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ServerResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // ServerStatusApplyConfiguration constructs a declarative configuration of the ServerStatus type for use with @@ -64,3 +66,11 @@ func (b *ServerStatusApplyConfiguration) WithResource(value *ServerResourceStatu b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ServerStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ServerStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go index 1cc5b6645..ff6fe4241 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servicespec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ServiceSpecApplyConfiguration represents a declarative configuration of the ServiceSpec type for use @@ -29,6 +30,7 @@ type ServiceSpecApplyConfiguration struct { Resource *ServiceResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ServiceSpecApplyConfiguration) WithManagedOptions(value *ManagedOptions return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ServiceSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ServiceSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go index 89fa866d4..b7bc37269 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/servicestatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // ServiceStatusApplyConfiguration represents a declarative configuration of the ServiceStatus type for use // with apply. type ServiceStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *ServiceResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ServiceResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // ServiceStatusApplyConfiguration constructs a declarative configuration of the ServiceStatus type for use with @@ -64,3 +66,11 @@ func (b *ServiceStatusApplyConfiguration) WithResource(value *ServiceResourceSta b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ServiceStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ServiceStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go index 0e271cc7d..ac675c764 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ShareNetworkSpecApplyConfiguration represents a declarative configuration of the ShareNetworkSpec type for use @@ -29,6 +30,7 @@ type ShareNetworkSpecApplyConfiguration struct { Resource *ShareNetworkResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *ShareNetworkSpecApplyConfiguration) WithManagedOptions(value *ManagedOp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *ShareNetworkSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *ShareNetworkSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go index 335bcfb77..e586e90ee 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/sharenetworkstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // ShareNetworkStatusApplyConfiguration represents a declarative configuration of the ShareNetworkStatus type for use // with apply. type ShareNetworkStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *ShareNetworkResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *ShareNetworkResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // ShareNetworkStatusApplyConfiguration constructs a declarative configuration of the ShareNetworkStatus type for use with @@ -64,3 +66,11 @@ func (b *ShareNetworkStatusApplyConfiguration) WithResource(value *ShareNetworkR b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *ShareNetworkStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *ShareNetworkStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go index 4c092dd01..c32747ef5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // SubnetSpecApplyConfiguration represents a declarative configuration of the SubnetSpec type for use @@ -29,6 +30,7 @@ type SubnetSpecApplyConfiguration struct { Resource *SubnetResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *SubnetSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsA return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *SubnetSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *SubnetSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go index e538e3724..fde0cef71 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/subnetstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // SubnetStatusApplyConfiguration represents a declarative configuration of the SubnetStatus type for use // with apply. type SubnetStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *SubnetResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *SubnetResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // SubnetStatusApplyConfiguration constructs a declarative configuration of the SubnetStatus type for use with @@ -64,3 +66,11 @@ func (b *SubnetStatusApplyConfiguration) WithResource(value *SubnetResourceStatu b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *SubnetStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *SubnetStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go index c744fe03f..12f298757 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // TrunkSpecApplyConfiguration represents a declarative configuration of the TrunkSpec type for use @@ -29,6 +30,7 @@ type TrunkSpecApplyConfiguration struct { Resource *TrunkResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *TrunkSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsAp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *TrunkSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *TrunkSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go index ffd42aeb8..5e7b3d922 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/trunkstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // 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"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *TrunkResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // TrunkStatusApplyConfiguration constructs a declarative configuration of the TrunkStatus type for use with @@ -64,3 +66,11 @@ func (b *TrunkStatusApplyConfiguration) WithResource(value *TrunkResourceStatusA b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *TrunkStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *TrunkStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go index fadcb620b..8ce4fb751 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userspec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // UserSpecApplyConfiguration represents a declarative configuration of the UserSpec type for use @@ -29,6 +30,7 @@ type UserSpecApplyConfiguration struct { Resource *UserResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *UserSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApp return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *UserSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *UserSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go index 1aae09224..d2e1194bf 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userstatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // 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"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *UserResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // UserStatusApplyConfiguration constructs a declarative configuration of the UserStatus type for use with @@ -64,3 +66,11 @@ func (b *UserStatusApplyConfiguration) WithResource(value *UserResourceStatusApp b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *UserStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *UserStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go index e0ffdcbca..208243749 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumespec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // VolumeSpecApplyConfiguration represents a declarative configuration of the VolumeSpec type for use @@ -29,6 +30,7 @@ type VolumeSpecApplyConfiguration struct { Resource *VolumeResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *VolumeSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsA return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *VolumeSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *VolumeSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go index 3e6be3743..fb12e4acf 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumestatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // VolumeStatusApplyConfiguration represents a declarative configuration of the VolumeStatus type for use // with apply. type VolumeStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *VolumeResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *VolumeResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // VolumeStatusApplyConfiguration constructs a declarative configuration of the VolumeStatus type for use with @@ -64,3 +66,11 @@ func (b *VolumeStatusApplyConfiguration) WithResource(value *VolumeResourceStatu b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *VolumeStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *VolumeStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go index d4540dcfd..42f263428 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypespec.go @@ -20,6 +20,7 @@ package v1alpha1 import ( apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // VolumeTypeSpecApplyConfiguration represents a declarative configuration of the VolumeTypeSpec type for use @@ -29,6 +30,7 @@ type VolumeTypeSpecApplyConfiguration struct { Resource *VolumeTypeResourceSpecApplyConfiguration `json:"resource,omitempty"` ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + ResyncPeriod *v1.Duration `json:"resyncPeriod,omitempty"` CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` } @@ -70,6 +72,14 @@ func (b *VolumeTypeSpecApplyConfiguration) WithManagedOptions(value *ManagedOpti return b } +// WithResyncPeriod sets the ResyncPeriod field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResyncPeriod field is set to the value of the last call. +func (b *VolumeTypeSpecApplyConfiguration) WithResyncPeriod(value v1.Duration) *VolumeTypeSpecApplyConfiguration { + b.ResyncPeriod = &value + return b +} + // WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CloudCredentialsRef field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go index 64a797263..b54ea49fc 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumetypestatus.go @@ -19,15 +19,17 @@ limitations under the License. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/applyconfigurations/meta/v1" ) // VolumeTypeStatusApplyConfiguration represents a declarative configuration of the VolumeTypeStatus type for use // with apply. type VolumeTypeStatusApplyConfiguration struct { - Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ID *string `json:"id,omitempty"` - Resource *VolumeTypeResourceStatusApplyConfiguration `json:"resource,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *VolumeTypeResourceStatusApplyConfiguration `json:"resource,omitempty"` + LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"` } // VolumeTypeStatusApplyConfiguration constructs a declarative configuration of the VolumeTypeStatus type for use with @@ -64,3 +66,11 @@ func (b *VolumeTypeStatusApplyConfiguration) WithResource(value *VolumeTypeResou b.Resource = value return b } + +// WithLastSyncTime sets the LastSyncTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastSyncTime field is set to the value of the last call. +func (b *VolumeTypeStatusApplyConfiguration) WithLastSyncTime(value metav1.Time) *VolumeTypeStatusApplyConfiguration { + b.LastSyncTime = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 8c0bc8003..9fc18f739 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -143,6 +143,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeStatus map: fields: @@ -157,6 +160,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.AddressScopeResourceStatus @@ -359,6 +365,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialStatus map: fields: @@ -373,6 +382,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ApplicationCredentialResourceStatus @@ -467,6 +479,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.DomainResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.DomainStatus map: fields: @@ -481,6 +496,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.DomainResourceStatus @@ -582,6 +600,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointStatus map: fields: @@ -596,6 +617,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.EndpointResourceStatus @@ -776,6 +800,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorStatus map: fields: @@ -790,6 +817,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FlavorResourceStatus @@ -962,6 +992,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FloatingIPResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FloatingIPStatus map: fields: @@ -976,6 +1009,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.FloatingIPResourceStatus @@ -1061,6 +1097,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.GroupResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.GroupStatus map: fields: @@ -1075,6 +1114,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.GroupResourceStatus @@ -1332,6 +1374,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ImageResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ImageStatus map: fields: @@ -1349,6 +1394,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ImageResourceStatus @@ -1434,6 +1482,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.KeyPairResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.KeyPairStatus map: fields: @@ -1448,6 +1499,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.KeyPairResourceStatus @@ -1650,6 +1704,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.NetworkResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.NetworkStatus map: fields: @@ -1664,6 +1721,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.NetworkResourceStatus @@ -1930,6 +1990,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PortResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PortStatus map: fields: @@ -1944,6 +2007,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PortResourceStatus @@ -2080,6 +2146,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ProjectResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ProjectStatus map: fields: @@ -2094,6 +2163,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ProjectResourceStatus @@ -2230,6 +2302,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentStatus map: fields: @@ -2241,6 +2316,9 @@ var schemaYAML = typed.YAMLObject(`types: elementRelationship: associative keys: - type + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceStatus @@ -2305,6 +2383,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleStatus map: fields: @@ -2319,6 +2400,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleResourceStatus @@ -2412,6 +2496,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RouterInterfaceSpec map: fields: + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: routerRef type: scalar: string @@ -2435,6 +2522,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RouterResourceSpec map: fields: @@ -2526,6 +2616,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RouterResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RouterStatus map: fields: @@ -2540,6 +2633,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RouterResourceStatus @@ -2739,6 +2835,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SecurityGroupResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SecurityGroupStatus map: fields: @@ -2753,6 +2852,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SecurityGroupResourceStatus @@ -2916,6 +3018,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerGroupResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerGroupStatus map: fields: @@ -2930,6 +3035,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerGroupResourceStatus @@ -3158,6 +3266,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerStatus map: fields: @@ -3172,6 +3283,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerResourceStatus @@ -3278,6 +3392,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServiceResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServiceStatus map: fields: @@ -3292,6 +3409,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServiceResourceStatus @@ -3405,6 +3525,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkStatus map: fields: @@ -3419,6 +3542,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ShareNetworkResourceStatus @@ -3665,6 +3791,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetStatus map: fields: @@ -3679,6 +3808,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.SubnetResourceStatus @@ -3848,6 +3980,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkStatus map: fields: @@ -3862,6 +3997,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.TrunkResourceStatus @@ -3998,6 +4136,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserStatus map: fields: @@ -4012,6 +4153,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus @@ -4219,6 +4363,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.VolumeResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.VolumeStatus map: fields: @@ -4233,6 +4380,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.VolumeResourceStatus @@ -4353,6 +4503,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.VolumeTypeResourceSpec + - name: resyncPeriod + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.VolumeTypeStatus map: fields: @@ -4367,6 +4520,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: id type: scalar: string + - name: lastSyncTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.VolumeTypeResourceStatus @@ -4395,6 +4551,8 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Duration + scalar: string - name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 map: elementType: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 9209db468..3cfdb8ac9 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -170,6 +170,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -189,6 +190,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[AddressScopeResourceStatus](#addressscoperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### AllocationPool @@ -435,6 +437,7 @@ _Appears in:_ | `resource` _[ApplicationCredentialResourceSpec](#applicationcredentialresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | Optional: \{\}
| | `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
Optional: \{\}
| | `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -454,6 +457,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ApplicationCredentialResourceStatus](#applicationcredentialresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### AvailabilityZoneHint @@ -661,6 +665,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -680,6 +685,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[DomainResourceStatus](#domainresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Endpoint @@ -797,6 +803,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -816,6 +823,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[EndpointResourceStatus](#endpointresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Ethertype @@ -1107,6 +1115,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -1126,6 +1135,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[FlavorResourceStatus](#flavorresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### FloatingIP @@ -1261,6 +1271,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -1280,6 +1291,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[FloatingIPResourceStatus](#floatingipresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Group @@ -1392,6 +1404,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -1411,6 +1424,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### HTTPMethod @@ -1922,6 +1936,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -1941,6 +1956,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ImageResourceStatus](#imageresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| | `downloadAttempts` _integer_ | downloadAttempts is the number of times the controller has attempted to download the image contents | | Optional: \{\}
| @@ -2107,6 +2123,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -2126,6 +2143,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[KeyPairResourceStatus](#keypairresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### KeystoneName @@ -2475,6 +2493,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -2494,6 +2513,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[NetworkResourceStatus](#networkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### NeutronDescription @@ -2866,6 +2886,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -2885,6 +2906,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[PortResourceStatus](#portresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### PortValueSpec @@ -3022,6 +3044,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -3041,6 +3064,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ProjectResourceStatus](#projectresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Protocol @@ -3239,6 +3263,7 @@ _Appears in:_ | `resource` _[RoleAssignmentResourceSpec](#roleassignmentresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | Optional: \{\}
| | `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
Optional: \{\}
| | `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -3257,6 +3282,7 @@ _Appears in:_ | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `resource` _[RoleAssignmentResourceStatus](#roleassignmentresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### RoleFilter @@ -3350,6 +3376,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -3369,6 +3396,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[RoleResourceStatus](#roleresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Router @@ -3468,6 +3496,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| #### RouterInterfaceStatus @@ -3485,6 +3514,7 @@ _Appears in:_ | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the port created for the router interface | | MaxLength: 1024
Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
of the resource. | | Optional: \{\}
| #### RouterInterfaceType @@ -3569,6 +3599,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -3588,6 +3619,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[RouterResourceStatus](#routerresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### RuleDirection @@ -3773,6 +3805,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -3792,6 +3825,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[SecurityGroupResourceStatus](#securitygroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Server @@ -4016,6 +4050,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4035,6 +4070,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ServerGroupResourceStatus](#servergroupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### ServerImport @@ -4240,6 +4276,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4259,6 +4296,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ServerResourceStatus](#serverresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### ServerTag @@ -4424,6 +4462,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4443,6 +4482,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ServiceResourceStatus](#serviceresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### ShareNetwork @@ -4564,6 +4604,7 @@ _Appears in:_ | `resource` _[ShareNetworkResourceSpec](#sharenetworkresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | Optional: \{\}
| | `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
Optional: \{\}
| | `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4583,6 +4624,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[ShareNetworkResourceStatus](#sharenetworkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Subnet @@ -4763,6 +4805,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4782,6 +4825,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[SubnetResourceStatus](#subnetresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Trunk @@ -4914,6 +4958,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -4933,6 +4978,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[TrunkResourceStatus](#trunkresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### TrunkSubportSpec @@ -5110,6 +5156,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -5129,6 +5176,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[UserResourceStatus](#userresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### Volume @@ -5319,6 +5367,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -5338,6 +5387,7 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[VolumeResourceStatus](#volumeresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| #### VolumeType @@ -5487,6 +5537,7 @@ _Appears in:_ | `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: \{\}
| +| `resyncPeriod` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#duration-v1-meta)_ | resyncPeriod defines how frequently the controller will re-reconcile
this resource even when no changes have been detected. This overrides
the global default resync period. The value must be a valid Go duration
string, e.g. "10m", "1h". Set to "0s" to disable periodic resync for
this resource. Very low values may cause excessive OpenStack API load. | | Optional: \{\}
| | `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| @@ -5506,5 +5557,6 @@ _Appears in:_ | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| | `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| | `resource` _[VolumeTypeResourceStatus](#volumetyperesourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| +| `lastSyncTime` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | lastSyncTime is the timestamp of the last successful reconciliation
that fetched state from OpenStack. It is updated each time the
controller successfully reads the resource state from the OpenStack
API. | | Optional: \{\}
| From c8d85942c4e41b150590bb6b8d13e96c2e6a2115 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 8 Jul 2026 17:19:09 +0300 Subject: [PATCH 228/237] Add generic periodic resync scheduling Wire the manager-level default resync period into every generated controller and make it part of the controller contract. Controllers resolve the effective period from the per-resource value first, then the global default, with zero disabling periodic resync. Add the generic scheduler and status update logic that records successful syncs, preserves pending timers after spurious events, applies positive-only jitter to spread load, and warns when the global default is set very low. --- cmd/manager/main.go | 3 + .../data/controller/controller.go.template | 14 +++- .../controllers/addressscope/controller.go | 14 +++- .../applicationcredential/controller.go | 14 +++- internal/controllers/domain/controller.go | 14 +++- internal/controllers/endpoint/controller.go | 14 +++- internal/controllers/flavor/controller.go | 14 +++- internal/controllers/floatingip/controller.go | 14 +++- .../generic/interfaces/controller.go | 2 + .../controllers/generic/interfaces/status.go | 12 ++- .../generic/reconciler/controller.go | 84 +++++++++++++++---- internal/controllers/generic/resync/config.go | 59 +++++++++++++ .../controllers/generic/resync/scheduler.go | 77 +++++++++++++++++ internal/controllers/generic/status/status.go | 31 ++++++- internal/controllers/group/controller.go | 14 +++- internal/controllers/image/controller.go | 13 ++- internal/controllers/keypair/controller.go | 14 +++- internal/controllers/network/controller.go | 14 +++- internal/controllers/port/controller.go | 14 +++- internal/controllers/project/controller.go | 14 +++- internal/controllers/role/controller.go | 14 +++- internal/controllers/router/controller.go | 14 +++- .../controllers/securitygroup/controller.go | 14 +++- internal/controllers/server/controller.go | 14 +++- .../controllers/servergroup/controller.go | 14 +++- internal/controllers/service/controller.go | 14 +++- .../controllers/sharenetwork/controller.go | 14 +++- internal/controllers/subnet/controller.go | 14 +++- internal/controllers/trunk/controller.go | 14 +++- internal/controllers/user/controller.go | 14 +++- internal/controllers/volume/controller.go | 14 +++- internal/controllers/volumetype/controller.go | 14 +++- internal/manager/manager.go | 11 +++ 33 files changed, 510 insertions(+), 118 deletions(-) create mode 100644 internal/controllers/generic/resync/config.go create mode 100644 internal/controllers/generic/resync/scheduler.go diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 97cd7e73a..6f759c2e5 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -82,6 +82,9 @@ func main() { flag.IntVar(&orcOpts.ScopeCacheMaxSize, "scope-cache-max-size", 10, "The maximum credentials count the operator should keep in cache. "+ "Setting this value to 0 means no cache.") + flag.DurationVar(&orcOpts.DefaultResyncPeriod, "default-resync-period", 0, + "Default resync period for all resources. Set to 0 to disable. "+ + "Can be overridden per-resource via spec.resyncPeriod.") flag.StringVar(&defaultCACertsPath, "default-ca-certs", "", "The path to a PEM-encoded CA Certificate file to supply as default for OpenStack API requests.") flag.Func("namespace", "A namespace that the controller watches to reconcile ORC objects. "+ diff --git a/cmd/scaffold-controller/data/controller/controller.go.template b/cmd/scaffold-controller/data/controller/controller.go.template index 1fdc04434..798ba52f5 100644 --- a/cmd/scaffold-controller/data/controller/controller.go.template +++ b/cmd/scaffold-controller/data/controller/controller.go.template @@ -19,6 +19,7 @@ package {{ .PackageName }} import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" {{- if or (len .AllCreateDependencies) (len .ImportDependencies) }} @@ -44,17 +45,22 @@ const controllerName = "{{ .PackageName }}" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources={{ .PackageName }}s/status,verbs=get;update;patch type {{ .PackageName }}ReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return {{ .PackageName }}ReconcilerConstructor{scopeFactory: scopeFactory} + return &{{ .PackageName }}ReconcilerConstructor{scopeFactory: scopeFactory} } func ({{ .PackageName }}ReconcilerConstructor) GetName() string { return controllerName } +func (c *{{ .PackageName }}ReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + {{- $kind := .Kind }} {{- $packageName := .PackageName }} {{- range .RequiredCreateDependencies }} @@ -100,7 +106,7 @@ var {{ $depNameCamelCase }}ImportDependency = dependency.NewDependency[*orcv1alp {{- end }} // SetupWithManager sets up the controller with the Manager. -func (c {{ .PackageName }}ReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *{{ .PackageName }}ReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) {{- if or (len .AllCreateDependencies) (len .ImportDependencies) }} k8sClient := mgr.GetClient() @@ -148,6 +154,6 @@ func (c {{ .PackageName }}ReconcilerConstructor) SetupWithManager(ctx context.Co return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, {{ .PackageName }}HelperFactory{}, {{ .PackageName }}StatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, {{ .PackageName }}HelperFactory{}, {{ .PackageName }}StatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/addressscope/controller.go b/internal/controllers/addressscope/controller.go index daa8694e6..718aa0f8b 100644 --- a/internal/controllers/addressscope/controller.go +++ b/internal/controllers/addressscope/controller.go @@ -19,6 +19,7 @@ package addressscope import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -40,17 +41,22 @@ const controllerName = "addressscope" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=addressscopes/status,verbs=get;update;patch type addressscopeReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return addressscopeReconcilerConstructor{scopeFactory: scopeFactory} + return &addressscopeReconcilerConstructor{scopeFactory: scopeFactory} } func (addressscopeReconcilerConstructor) GetName() string { return controllerName } +func (c *addressscopeReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.AddressScopeList, *orcv1alpha1.Project]( "spec.resource.projectRef", func(addressscope *orcv1alpha1.AddressScope) []string { @@ -75,7 +81,7 @@ var projectImportDependency = dependency.NewDependency[*orcv1alpha1.AddressScope ) // SetupWithManager sets up the controller with the Manager. -func (c addressscopeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *addressscopeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -109,6 +115,6 @@ func (c addressscopeReconcilerConstructor) SetupWithManager(ctx context.Context, return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, addressscopeHelperFactory{}, addressscopeStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, addressscopeHelperFactory{}, addressscopeStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/applicationcredential/controller.go b/internal/controllers/applicationcredential/controller.go index 4e41a989f..c39cf1d47 100644 --- a/internal/controllers/applicationcredential/controller.go +++ b/internal/controllers/applicationcredential/controller.go @@ -19,6 +19,7 @@ package applicationcredential import ( "context" "errors" + "time" corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" @@ -94,17 +95,22 @@ var ( ) type applicationcredentialReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return applicationcredentialReconcilerConstructor{scopeFactory: scopeFactory} + return &applicationcredentialReconcilerConstructor{scopeFactory: scopeFactory} } func (applicationcredentialReconcilerConstructor) GetName() string { return controllerName } +func (c *applicationcredentialReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var userDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ApplicationCredentialList, *orcv1alpha1.User]( "spec.resource.userRef", func(applicationcredential *orcv1alpha1.ApplicationCredential) []string { @@ -129,7 +135,7 @@ var userImportDependency = dependency.NewDependency[*orcv1alpha1.ApplicationCred ) // SetupWithManager sets up the controller with the Manager. -func (c applicationcredentialReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *applicationcredentialReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -195,6 +201,6 @@ func (c applicationcredentialReconcilerConstructor) SetupWithManager(ctx context return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, applicationcredentialHelperFactory{}, applicationcredentialStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, applicationcredentialHelperFactory{}, applicationcredentialStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/domain/controller.go b/internal/controllers/domain/controller.go index 38c831aa2..6dfee5eb0 100644 --- a/internal/controllers/domain/controller.go +++ b/internal/controllers/domain/controller.go @@ -19,6 +19,7 @@ package domain import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -37,19 +38,24 @@ const controllerName = "domain" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=domains/status,verbs=get;update;patch type domainReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return domainReconcilerConstructor{scopeFactory: scopeFactory} + return &domainReconcilerConstructor{scopeFactory: scopeFactory} } func (domainReconcilerConstructor) GetName() string { return controllerName } +func (c *domainReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c domainReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *domainReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -63,6 +69,6 @@ func (c domainReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, domainHelperFactory{}, domainStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, domainHelperFactory{}, domainStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/endpoint/controller.go b/internal/controllers/endpoint/controller.go index f1939076e..727cb1025 100644 --- a/internal/controllers/endpoint/controller.go +++ b/internal/controllers/endpoint/controller.go @@ -19,6 +19,7 @@ package endpoint import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -40,17 +41,22 @@ const controllerName = "endpoint" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=endpoints/status,verbs=get;update;patch type endpointReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return endpointReconcilerConstructor{scopeFactory: scopeFactory} + return &endpointReconcilerConstructor{scopeFactory: scopeFactory} } func (endpointReconcilerConstructor) GetName() string { return controllerName } +func (c *endpointReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var serviceDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.EndpointList, *orcv1alpha1.Service]( "spec.resource.serviceRef", func(endpoint *orcv1alpha1.Endpoint) []string { @@ -75,7 +81,7 @@ var serviceImportDependency = dependency.NewDependency[*orcv1alpha1.EndpointList ) // SetupWithManager sets up the controller with the Manager. -func (c endpointReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *endpointReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -109,6 +115,6 @@ func (c endpointReconcilerConstructor) SetupWithManager(ctx context.Context, mgr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, endpointHelperFactory{}, endpointStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, endpointHelperFactory{}, endpointStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/flavor/controller.go b/internal/controllers/flavor/controller.go index 3b3cd459d..0f76371dd 100644 --- a/internal/controllers/flavor/controller.go +++ b/internal/controllers/flavor/controller.go @@ -19,6 +19,7 @@ package flavor import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -37,19 +38,24 @@ const controllerName = "flavor" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=flavors/status,verbs=get;update;patch type flavorReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return flavorReconcilerConstructor{scopeFactory: scopeFactory} + return &flavorReconcilerConstructor{scopeFactory: scopeFactory} } func (flavorReconcilerConstructor) GetName() string { return controllerName } +func (c *flavorReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c flavorReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *flavorReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -63,6 +69,6 @@ func (c flavorReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, flavorHelperFactory{}, flavorStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, flavorHelperFactory{}, flavorStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/floatingip/controller.go b/internal/controllers/floatingip/controller.go index 6cf68e27b..a573ec340 100644 --- a/internal/controllers/floatingip/controller.go +++ b/internal/controllers/floatingip/controller.go @@ -19,6 +19,7 @@ package floatingip import ( "context" "errors" + "time" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" @@ -39,17 +40,22 @@ import ( // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=floatingips/status,verbs=get;update;patch type floatingipReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return floatingipReconcilerConstructor{scopeFactory: scopeFactory} + return &floatingipReconcilerConstructor{scopeFactory: scopeFactory} } func (floatingipReconcilerConstructor) GetName() string { return controllerName } +func (c *floatingipReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + const controllerName = "floatingip" var ( @@ -136,7 +142,7 @@ var ( ) // SetupWithManager sets up the controller with the Manager. -func (c floatingipReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *floatingipReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := mgr.GetLogger().WithValues("controller", controllerName) k8sClient := mgr.GetClient() @@ -217,6 +223,6 @@ func (c floatingipReconcilerConstructor) SetupWithManager(ctx context.Context, m return err } - r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, floatingipHelperFactory{}, floatingipStatusWriter{}) + r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, floatingipHelperFactory{}, floatingipStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/generic/interfaces/controller.go b/internal/controllers/generic/interfaces/controller.go index 87f81ccef..26f413ed8 100644 --- a/internal/controllers/generic/interfaces/controller.go +++ b/internal/controllers/generic/interfaces/controller.go @@ -18,6 +18,7 @@ package interfaces import ( "context" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,6 +30,7 @@ import ( type Controller interface { SetupWithManager(context.Context, ctrl.Manager, controller.Options) error GetName() string + SetDefaultResyncPeriod(time.Duration) } type ResourceController interface { diff --git a/internal/controllers/generic/interfaces/status.go b/internal/controllers/generic/interfaces/status.go index 6f7088760..15cdf3fd4 100644 --- a/internal/controllers/generic/interfaces/status.go +++ b/internal/controllers/generic/interfaces/status.go @@ -40,12 +40,20 @@ type ORCStatusApplyConfig[statusApplyPT any] interface { WithConditions(...*applyconfigv1.ConditionApplyConfiguration) statusApplyPT } -// ORCStatusApplyConfigWithID extends ORCStatusApplyConfig with an ID field. +// ORCStatusApplyConfigWithLastSyncTime extends ORCStatusApplyConfig with a +// LastSyncTime field. +type ORCStatusApplyConfigWithLastSyncTime[statusApplyPT any] interface { + ORCStatusApplyConfig[statusApplyPT] + WithLastSyncTime(metav1.Time) statusApplyPT +} + +// ORCStatusApplyConfigWithID extends ORCStatusApplyConfigWithLastSyncTime with +// an ID field. // This is required by resources that have an OpenStack-assigned ID stored in // status.id. Resources without an ID (e.g. relationship resources like // RoleAssignment) use only ORCStatusApplyConfig. type ORCStatusApplyConfigWithID[statusApplyPT any] interface { - ORCStatusApplyConfig[statusApplyPT] + ORCStatusApplyConfigWithLastSyncTime[statusApplyPT] WithID(id string) statusApplyPT } diff --git a/internal/controllers/generic/reconciler/controller.go b/internal/controllers/generic/reconciler/controller.go index d74a6f18d..e1c1a9ed1 100644 --- a/internal/controllers/generic/reconciler/controller.go +++ b/internal/controllers/generic/reconciler/controller.go @@ -19,6 +19,7 @@ package reconciler import ( "context" "fmt" + "time" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -29,6 +30,7 @@ import ( 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/controllers/generic/resync" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/status" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/scope" @@ -58,13 +60,15 @@ func NewController[ name string, k8sClient client.Client, scopeFactory scope.Factory, helperFactory interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT], statusWriter interfaces.ResourceStatusWriter[orcObjectPT, *osResourceT, objectApplyPT, statusApplyPT], + defaultResyncPeriod time.Duration, ) Controller[orcObjectPT, orcObjectT, resourceSpecT, filterT, objectApplyPT, statusApplyPT, statusApplyT, osResourceT] { return Controller[orcObjectPT, orcObjectT, resourceSpecT, filterT, objectApplyPT, statusApplyPT, statusApplyT, osResourceT]{ - name: name, - client: k8sClient, - scopeFactory: scopeFactory, - helperFactory: helperFactory, - statusWriter: statusWriter, + name: name, + client: k8sClient, + scopeFactory: scopeFactory, + helperFactory: helperFactory, + statusWriter: statusWriter, + defaultResyncPeriod: defaultResyncPeriod, } } @@ -91,6 +95,13 @@ type Controller[ helperFactory interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] statusWriter interfaces.ResourceStatusWriter[orcObjectPT, *osResourceT, objectApplyPT, statusApplyPT] + + // defaultResyncPeriod is the operator-level default resync period passed + // from the manager options. It is used as the fallback in + // resync.DetermineResyncPeriod when a resource does not specify its own + // spec.resyncPeriod. A value of 0 means periodic resync is disabled by + // default. + defaultResyncPeriod time.Duration } func (c *Controller[_, _, _, _, _, _, _, _]) GetName() string { @@ -131,7 +142,7 @@ func (c *Controller[ return c.reconcileNormal(ctx, adapter).Return(log) } -// shouldReconcile filters events when the object status is up to date, and its +// ShouldReconcile filters events when the object status is up to date, and its // status indicates that no further reconciliation is required. // // Specifically it looks at the Progressing condition. It has the following behaviour: @@ -140,10 +151,22 @@ func (c *Controller[ // - Progressing condition is present and False, but observedGeneration is old -> reconcile // - Progressing condition is false and observedGeneration is up to date -> do not reconcile // -// If shouldReconcile is preventing an object from being reconciled which should +// If resyncPeriod > 0, periodic resync is also considered: +// - If lastSyncTime is nil (never synced), reconcile immediately. +// - If time.Since(lastSyncTime) >= resyncPeriod, a resync is due: reconcile. +// - If time.Since(lastSyncTime) < resyncPeriod, the next resync is not yet due: +// do not reconcile (unless condition-based logic above requires it). +// +// When resyncPeriod <= 0 (disabled), resync logic is not applied and the +// existing condition-based behaviour is unchanged. +// +// The resync check uses the persisted lastSyncTime so that controller restarts +// respect the time already elapsed, preventing a thundering herd. +// +// If ShouldReconcile is preventing an object from being reconciled which should // be reconciled, consider if that object's actuator is correctly returning a // ProgressStatus indicating that the reconciliation should continue. -func shouldReconcile(obj orcv1alpha1.ObjectWithConditions) bool { +func ShouldReconcile(obj orcv1alpha1.ObjectWithConditions, lastSyncTime *metav1.Time, resyncPeriod time.Duration) bool { progressing := meta.FindStatusCondition(obj.GetConditions(), orcv1alpha1.ConditionProgressing) if progressing == nil { return true @@ -153,7 +176,22 @@ func shouldReconcile(obj orcv1alpha1.ObjectWithConditions) bool { return true } - return progressing.ObservedGeneration != obj.GetGeneration() + if progressing.ObservedGeneration != obj.GetGeneration() { + return true + } + + // Condition-based check says no reconcile is needed. Now check if a + // periodic resync is due. + if resyncPeriod > 0 { + // Never synced: reconcile immediately. + if lastSyncTime == nil { + return true + } + // Resync is due when the elapsed time has reached the period. + return time.Since(lastSyncTime.Time) >= resyncPeriod + } + + return false } func (c *Controller[ @@ -168,8 +206,12 @@ func (c *Controller[ // We do this here rather than in a predicate because predicates only cover // a single watch. Doing it here means we cover all sources of // reconciliation, including our dependencies. - if !shouldReconcile(objAdapter.GetObject()) { + effectiveResyncPeriod := resync.DetermineResyncPeriod(objAdapter.GetResyncPeriod(), c.defaultResyncPeriod) + if !ShouldReconcile(objAdapter.GetObject(), objAdapter.GetLastSyncTime(), effectiveResyncPeriod) { log.V(logging.Verbose).Info("Status is up to date: not reconciling") + if remaining := resync.RemainingUntilNextSync(objAdapter.GetLastSyncTime(), effectiveResyncPeriod); remaining > 0 { + return reconcileStatus.WithRequeue(remaining) + } return reconcileStatus } @@ -192,6 +234,15 @@ func (c *Controller[ } osResource, getOSResourceRS := GetOrCreateOSResource(ctx, log, c, objAdapter, actuator) + if getOSResourceRS.IsExternallyDeleted() { + if objAdapter.GetStatusID() != nil { + log.V(logging.Info).Info("Clearing status.id after external deletion to enable recreation") + if err := status.ClearStatusID(ctx, c, objAdapter.GetObject()); err != nil { + return reconcileStatus.WithError(fmt.Errorf("clearing status ID after external deletion: %w", err)) + } + } + return reconcileStatus.WithProgressMessage("OpenStack resource was deleted externally; will recreate on next reconcile") + } if needsReschedule, err := getOSResourceRS.NeedsReschedule(); needsReschedule { if err == nil { log.V(logging.Verbose).Info("Waiting on events before creation") @@ -199,11 +250,6 @@ func (c *Controller[ return getOSResourceRS.WithReconcileStatus(reconcileStatus) } - if osResource == nil { - // Programming error: if we don't have a resource we should either have an error or be waiting on something - return reconcileStatus.WithError(fmt.Errorf("oResource is not set, but no wait events or error")) - } - if objAdapter.GetStatusID() == nil { resourceID := actuator.GetResourceID(osResource) if err := status.SetStatusID(ctx, c, objAdapter.GetObject(), resourceID, c.statusWriter); err != nil { @@ -229,6 +275,14 @@ func (c *Controller[ } } + // Schedule a resync requeue when the effective resync period is configured, + // there is no terminal error, and no other requeue is already pending. + // Positive-only jitter of [0%, +20%] is applied to spread load across + // resources sharing the same period. + if resync.ShouldScheduleResync(effectiveResyncPeriod, reconcileStatus) { + reconcileStatus = reconcileStatus.WithRequeue(resync.CalculateJitteredDuration(effectiveResyncPeriod)) + } + return reconcileStatus } diff --git a/internal/controllers/generic/resync/config.go b/internal/controllers/generic/resync/config.go new file mode 100644 index 000000000..3b5994962 --- /dev/null +++ b/internal/controllers/generic/resync/config.go @@ -0,0 +1,59 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 resync provides helpers for determining the effective resync period +// for ORC controllers, implementing the configuration resolution hierarchy. +package resync + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// DetermineResyncPeriod resolves the effective resync period using the +// following hierarchy: +// +// 1. If specValue is non-nil and non-zero, return its duration (per-resource +// override takes precedence). +// 2. If specValue is explicitly zero (0s), return 0 (resync is disabled +// regardless of the global default). +// 3. If specValue is nil, return globalDefault. +// +// A return value of 0 means periodic resync is disabled. +func DetermineResyncPeriod(specValue *metav1.Duration, globalDefault time.Duration) time.Duration { + if specValue != nil { + // Explicit spec value: use it unconditionally (zero means disabled). + return specValue.Duration + } + // No per-resource override: fall back to the global default. + return globalDefault +} + +// RemainingUntilNextSync returns how long remains before the next periodic +// resync is due. It returns 0 when periodic resync is disabled, lastSyncTime is +// unset, or the period has already elapsed. +func RemainingUntilNextSync(lastSyncTime *metav1.Time, resyncPeriod time.Duration) time.Duration { + if resyncPeriod <= 0 || lastSyncTime == nil { + return 0 + } + + remaining := resyncPeriod - time.Since(lastSyncTime.Time) + if remaining <= 0 { + return 0 + } + return remaining +} diff --git a/internal/controllers/generic/resync/scheduler.go b/internal/controllers/generic/resync/scheduler.go new file mode 100644 index 000000000..1530ba323 --- /dev/null +++ b/internal/controllers/generic/resync/scheduler.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 resync + +import ( + "errors" + "time" + + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +const ( + // jitterFactor is the maximum fraction of extra time added to the base + // duration. A value of 0.2 produces durations in [base, base*1.2). + // Positive-only jitter ensures the requeue always fires after + // resyncPeriod has elapsed, so shouldReconcile always returns true + // when the requeue fires. + jitterFactor = 0.2 +) + +// CalculateJitteredDuration returns a duration in the range [base, base*1.2) +// using uniform random positive-only jitter. Jitter prevents thundering-herd +// problems when many resources share the same resync period. +func CalculateJitteredDuration(base time.Duration) time.Duration { + return wait.Jitter(base, jitterFactor) +} + +// ShouldScheduleResync reports whether a periodic resync should be scheduled +// based on the effective resync period and the current reconcile status. +// +// It returns false (do not schedule) when: +// - resyncPeriod <= 0: periodic resync is disabled. +// - reconcileStatus contains a terminal error: the resource is in a +// non-retryable error state; resync would be pointless. +// - reconcileStatus already requests a requeue: another reconcile is +// already pending so a resync requeue would be redundant. +// +// When it returns true, the caller should schedule a requeue after +// CalculateJitteredDuration(resyncPeriod). +func ShouldScheduleResync(resyncPeriod time.Duration, reconcileStatus progress.ReconcileStatus) bool { + // Resync disabled. + if resyncPeriod <= 0 { + return false + } + + // Terminal error: no further reconciles will help. + if err := reconcileStatus.GetError(); err != nil { + var terminalError *orcerrors.TerminalError + if errors.As(err, &terminalError) { + return false + } + } + + // Another requeue is already pending; avoid adding a redundant one. + if reconcileStatus.GetRequeue() > 0 { + return false + } + + return true +} diff --git a/internal/controllers/generic/status/status.go b/internal/controllers/generic/status/status.go index 990eb1ed0..f2c3c4d39 100644 --- a/internal/controllers/generic/status/status.go +++ b/internal/controllers/generic/status/status.go @@ -62,6 +62,28 @@ func SetStatusID[ return controller.GetK8sClient().Status().Patch(ctx, orcObject, applyconfigs.Patch(types.MergePatchType, applyConfig)) } +// ClearStatusID clears the status.id field of an ORC object using a JSON merge +// patch. This is necessary when an externally deleted managed resource is +// detected: clearing the ID allows the next reconciliation to enter the +// standard creation path and assign a new ID after the resource is recreated. +// +// A JSON merge patch with an explicit null value is required because the +// generated apply configuration types use omitempty on the ID field, meaning a +// nil pointer would simply omit the field rather than clear it. +func ClearStatusID(ctx context.Context, controller interfaces.ResourceController, orcObject client.Object) error { + patch := client.RawPatch(types.MergePatchType, []byte(`{"status":{"id":null}}`)) + return controller.GetK8sClient().Status().Patch(ctx, orcObject, patch) +} + +// shouldSetLastSyncTime reports whether lastSyncTime should be set on a status +// update. It returns true only when the reconciliation completed successfully: +// the reconcileStatus contains neither errors nor progress messages. A requeue +// alone (e.g., for a periodic resync) does not prevent the update. +func shouldSetLastSyncTime(reconcileStatus progress.ReconcileStatus) bool { + needsReschedule, _ := reconcileStatus.NeedsReschedule() + return !needsReschedule +} + func UpdateStatus[ orcObjectPT interface { client.Object @@ -70,7 +92,7 @@ func UpdateStatus[ osResourcePT *osResourceT, objectApplyPT interfaces.ORCApplyConfig[objectApplyPT, statusApplyPT], statusApplyPT interface { - interfaces.ORCStatusApplyConfig[statusApplyPT] + interfaces.ORCStatusApplyConfigWithLastSyncTime[statusApplyPT] *statusApply }, statusApply any, @@ -100,6 +122,13 @@ func UpdateStatus[ reconcileStatus = reconcileStatus.WithReconcileStatus(availableReconcileStatus) SetCommonConditions(orcObject, applyConfigStatus, available, reconcileStatus, now) + // Set lastSyncTime only on successful reconciliation: no errors and no + // progress messages indicate that the controller successfully fetched the + // resource state from OpenStack. + if shouldSetLastSyncTime(reconcileStatus) { + applyConfigStatus.WithLastSyncTime(now) + } + // Patch orcObject with the status transaction k8sClient := controller.GetK8sClient() ssaFieldOwner := orcstrings.GetSSAFieldOwnerWithTxn(controller.GetName(), orcstrings.SSATransactionStatus) diff --git a/internal/controllers/group/controller.go b/internal/controllers/group/controller.go index 043a22e44..b5378e044 100644 --- a/internal/controllers/group/controller.go +++ b/internal/controllers/group/controller.go @@ -19,6 +19,7 @@ package group import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -40,17 +41,22 @@ const controllerName = "group" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=groups/status,verbs=get;update;patch type groupReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return groupReconcilerConstructor{scopeFactory: scopeFactory} + return &groupReconcilerConstructor{scopeFactory: scopeFactory} } func (groupReconcilerConstructor) GetName() string { return controllerName } +func (c *groupReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.GroupList, *orcv1alpha1.Domain]( "spec.resource.domainRef", func(group *orcv1alpha1.Group) []string { @@ -75,7 +81,7 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.GroupList, *o ) // SetupWithManager sets up the controller with the Manager. -func (c groupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *groupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -109,6 +115,6 @@ func (c groupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ct return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, groupHelperFactory{}, groupStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, groupHelperFactory{}, groupStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/image/controller.go b/internal/controllers/image/controller.go index 2416fa823..f70f3ccdf 100644 --- a/internal/controllers/image/controller.go +++ b/internal/controllers/image/controller.go @@ -49,19 +49,24 @@ const ( ) type imageReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return imageReconcilerConstructor{scopeFactory: scopeFactory} + return &imageReconcilerConstructor{scopeFactory: scopeFactory} } func (imageReconcilerConstructor) GetName() string { return controllerName } +func (c *imageReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c imageReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *imageReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -75,6 +80,6 @@ func (c imageReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ct return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, imageHelperFactory{}, imageStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, imageHelperFactory{}, imageStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/keypair/controller.go b/internal/controllers/keypair/controller.go index 64cc7cb73..2d14c04cd 100644 --- a/internal/controllers/keypair/controller.go +++ b/internal/controllers/keypair/controller.go @@ -19,6 +19,7 @@ package keypair import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -37,19 +38,24 @@ const controllerName = "keypair" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=keypairs/status,verbs=get;update;patch type keypairReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return keypairReconcilerConstructor{scopeFactory: scopeFactory} + return &keypairReconcilerConstructor{scopeFactory: scopeFactory} } func (keypairReconcilerConstructor) GetName() string { return controllerName } +func (c *keypairReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c keypairReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *keypairReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -63,6 +69,6 @@ func (c keypairReconcilerConstructor) SetupWithManager(ctx context.Context, mgr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, keypairHelperFactory{}, keypairStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, keypairHelperFactory{}, keypairStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/network/controller.go b/internal/controllers/network/controller.go index a9795133e..f08fdaf0f 100644 --- a/internal/controllers/network/controller.go +++ b/internal/controllers/network/controller.go @@ -19,6 +19,7 @@ package network import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -65,11 +66,12 @@ var ( ) type networkReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return networkReconcilerConstructor{ + return &networkReconcilerConstructor{ scopeFactory: scopeFactory, } } @@ -78,8 +80,12 @@ func (networkReconcilerConstructor) GetName() string { return controllerName } +func (c *networkReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c networkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *networkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -113,6 +119,6 @@ func (c networkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, networkHelperFactory{}, networkStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, networkHelperFactory{}, networkStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/port/controller.go b/internal/controllers/port/controller.go index 2d9f881fc..5da5ca1d1 100644 --- a/internal/controllers/port/controller.go +++ b/internal/controllers/port/controller.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -262,19 +263,24 @@ func serverToPortMapFunc(ctx context.Context, k8sClient client.Client) handler.M } type portReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return portReconcilerConstructor{scopeFactory: scopeFactory} + return &portReconcilerConstructor{scopeFactory: scopeFactory} } func (portReconcilerConstructor) GetName() string { return controllerName } +func (c *portReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := mgr.GetLogger().WithValues("controller", controllerName) k8sClient := mgr.GetClient() @@ -357,6 +363,6 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr return err } - r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, portHelperFactory{}, portStatusWriter{}) + r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, portHelperFactory{}, portStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/project/controller.go b/internal/controllers/project/controller.go index 116b2024e..74dddc532 100644 --- a/internal/controllers/project/controller.go +++ b/internal/controllers/project/controller.go @@ -19,6 +19,7 @@ package project import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -40,17 +41,22 @@ const controllerName = "project" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=projects/status,verbs=get;update;patch type projectReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return projectReconcilerConstructor{scopeFactory: scopeFactory} + return &projectReconcilerConstructor{scopeFactory: scopeFactory} } func (projectReconcilerConstructor) GetName() string { return controllerName } +func (c *projectReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ProjectList, *orcv1alpha1.Domain]( "spec.resource.domainRef", func(project *orcv1alpha1.Project) []string { @@ -75,7 +81,7 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.ProjectList, ) // SetupWithManager sets up the controller with the Manager. -func (c projectReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *projectReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -109,6 +115,6 @@ func (c projectReconcilerConstructor) SetupWithManager(ctx context.Context, mgr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, projectHelperFactory{}, projectStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, projectHelperFactory{}, projectStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/role/controller.go b/internal/controllers/role/controller.go index faa40bfc0..d4a1b241e 100644 --- a/internal/controllers/role/controller.go +++ b/internal/controllers/role/controller.go @@ -19,6 +19,7 @@ package role import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -40,17 +41,22 @@ const controllerName = "role" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roles/status,verbs=get;update;patch type roleReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return roleReconcilerConstructor{scopeFactory: scopeFactory} + return &roleReconcilerConstructor{scopeFactory: scopeFactory} } func (roleReconcilerConstructor) GetName() string { return controllerName } +func (c *roleReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleList, *orcv1alpha1.Domain]( "spec.resource.domainRef", func(role *orcv1alpha1.Role) []string { @@ -75,7 +81,7 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.RoleList, *or ) // SetupWithManager sets up the controller with the Manager. -func (c roleReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *roleReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -109,6 +115,6 @@ func (c roleReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, roleHelperFactory{}, roleStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, roleHelperFactory{}, roleStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/router/controller.go b/internal/controllers/router/controller.go index 3b11b3192..2509bd4de 100644 --- a/internal/controllers/router/controller.go +++ b/internal/controllers/router/controller.go @@ -19,6 +19,7 @@ package router import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -38,17 +39,22 @@ import ( // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=routers/status,verbs=get;update;patch type routerReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return routerReconcilerConstructor{scopeFactory: scopeFactory} + return &routerReconcilerConstructor{scopeFactory: scopeFactory} } func (routerReconcilerConstructor) GetName() string { return controllerName } +func (c *routerReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + const controllerName = "router" var ( @@ -95,7 +101,7 @@ var ( ) // SetupWithManager sets up the controller with the Manager. -func (c routerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *routerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := mgr.GetLogger().WithValues("controller", controllerName) k8sClient := mgr.GetClient() @@ -138,6 +144,6 @@ func (c routerReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } - r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, routerHelperFactory{}, routerStatusWriter{}) + r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, routerHelperFactory{}, routerStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/securitygroup/controller.go b/internal/controllers/securitygroup/controller.go index 2e5d525e5..d52103ce5 100644 --- a/internal/controllers/securitygroup/controller.go +++ b/internal/controllers/securitygroup/controller.go @@ -19,6 +19,7 @@ package securitygroup import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -65,11 +66,12 @@ var ( ) type securitygroupReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return securitygroupReconcilerConstructor{ + return &securitygroupReconcilerConstructor{ scopeFactory: scopeFactory, } } @@ -78,8 +80,12 @@ func (securitygroupReconcilerConstructor) GetName() string { return controllerName } +func (c *securitygroupReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c securitygroupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *securitygroupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -113,7 +119,7 @@ func (c securitygroupReconcilerConstructor) SetupWithManager(ctx context.Context return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, securityGroupHelperFactory{}, securityGroupStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, securityGroupHelperFactory{}, securityGroupStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/server/controller.go b/internal/controllers/server/controller.go index b59bc258b..71f7ce311 100644 --- a/internal/controllers/server/controller.go +++ b/internal/controllers/server/controller.go @@ -19,6 +19,7 @@ package server import ( "context" "errors" + "time" corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" @@ -38,17 +39,22 @@ import ( // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=servers/status,verbs=get;update;patch type serverReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return serverReconcilerConstructor{scopeFactory: scopeFactory} + return &serverReconcilerConstructor{scopeFactory: scopeFactory} } func (serverReconcilerConstructor) GetName() string { return controllerName } +func (c *serverReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + const controllerName = "server" var ( @@ -216,7 +222,7 @@ var ( ) // SetupWithManager sets up the controller with the Manager. -func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := mgr.GetLogger().WithValues("controller", controllerName) k8sClient := mgr.GetClient() @@ -317,6 +323,6 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } - r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, serverHelperFactory{}, serverStatusWriter{}) + r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, serverHelperFactory{}, serverStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/servergroup/controller.go b/internal/controllers/servergroup/controller.go index 98069dc81..fa63d245d 100644 --- a/internal/controllers/servergroup/controller.go +++ b/internal/controllers/servergroup/controller.go @@ -19,6 +19,7 @@ package servergroup import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -37,19 +38,24 @@ const controllerName = "servergroup" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=servergroups/status,verbs=get;update;patch type servergroupReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return servergroupReconcilerConstructor{scopeFactory: scopeFactory} + return &servergroupReconcilerConstructor{scopeFactory: scopeFactory} } func (servergroupReconcilerConstructor) GetName() string { return controllerName } +func (c *servergroupReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c servergroupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *servergroupReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -63,6 +69,6 @@ func (c servergroupReconcilerConstructor) SetupWithManager(ctx context.Context, return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, servergroupHelperFactory{}, servergroupStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, servergroupHelperFactory{}, servergroupStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/service/controller.go b/internal/controllers/service/controller.go index 6e46a0dbd..195d4988d 100644 --- a/internal/controllers/service/controller.go +++ b/internal/controllers/service/controller.go @@ -19,6 +19,7 @@ package service import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -37,19 +38,24 @@ const controllerName = "service" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=services/status,verbs=get;update;patch type serviceReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return serviceReconcilerConstructor{scopeFactory: scopeFactory} + return &serviceReconcilerConstructor{scopeFactory: scopeFactory} } func (serviceReconcilerConstructor) GetName() string { return controllerName } +func (c *serviceReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c serviceReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *serviceReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -63,6 +69,6 @@ func (c serviceReconcilerConstructor) SetupWithManager(ctx context.Context, mgr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, serviceHelperFactory{}, serviceStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, serviceHelperFactory{}, serviceStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/sharenetwork/controller.go b/internal/controllers/sharenetwork/controller.go index aa6fbf1b5..a1a970330 100644 --- a/internal/controllers/sharenetwork/controller.go +++ b/internal/controllers/sharenetwork/controller.go @@ -19,6 +19,7 @@ package sharenetwork import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -40,17 +41,22 @@ const controllerName = "sharenetwork" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=sharenetworks/status,verbs=get;update;patch type sharenetworkReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return sharenetworkReconcilerConstructor{scopeFactory: scopeFactory} + return &sharenetworkReconcilerConstructor{scopeFactory: scopeFactory} } func (sharenetworkReconcilerConstructor) GetName() string { return controllerName } +func (c *sharenetworkReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var networkDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ShareNetworkList, *orcv1alpha1.Network]( "spec.resource.networkRef", func(sharenetwork *orcv1alpha1.ShareNetwork) []string { @@ -76,7 +82,7 @@ var subnetDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ShareN ) // SetupWithManager sets up the controller with the Manager. -func (c sharenetworkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *sharenetworkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -109,6 +115,6 @@ func (c sharenetworkReconcilerConstructor) SetupWithManager(ctx context.Context, return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, sharenetworkHelperFactory{}, sharenetworkStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, sharenetworkHelperFactory{}, sharenetworkStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/subnet/controller.go b/internal/controllers/subnet/controller.go index ea8eb33f0..b8d039dcf 100644 --- a/internal/controllers/subnet/controller.go +++ b/internal/controllers/subnet/controller.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "time" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" @@ -40,17 +41,22 @@ import ( ) type subnetReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return subnetReconcilerConstructor{scopeFactory: scopeFactory} + return &subnetReconcilerConstructor{scopeFactory: scopeFactory} } func (subnetReconcilerConstructor) GetName() string { return controllerName } +func (c *subnetReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + const controllerName = "subnet" var ( @@ -114,7 +120,7 @@ var ( ) // SetupWithManager sets up the controller with the Manager. -func (c subnetReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *subnetReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { controllerName := c.GetName() log := mgr.GetLogger().WithValues("controller", controllerName) k8sClient := mgr.GetClient() @@ -195,6 +201,6 @@ func (c subnetReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } - r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, subnetHelperFactory{}, subnetStatusWriter{}) + r := reconciler.NewController(controllerName, k8sClient, c.scopeFactory, subnetHelperFactory{}, subnetStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/trunk/controller.go b/internal/controllers/trunk/controller.go index ce8b13f2e..95b0c23a2 100644 --- a/internal/controllers/trunk/controller.go +++ b/internal/controllers/trunk/controller.go @@ -19,6 +19,7 @@ package trunk import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -41,17 +42,22 @@ const controllerName = "trunk" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=trunks/status,verbs=get;update;patch type trunkReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return trunkReconcilerConstructor{scopeFactory: scopeFactory} + return &trunkReconcilerConstructor{scopeFactory: scopeFactory} } func (trunkReconcilerConstructor) GetName() string { return controllerName } +func (c *trunkReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var portDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.TrunkList, *orcv1alpha1.Port]( "spec.resource.portRef", func(trunk *orcv1alpha1.Trunk) []string { @@ -119,7 +125,7 @@ var subportPortDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.T ) // SetupWithManager sets up the controller with the Manager. -func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -182,6 +188,6 @@ func (c trunkReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ct return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, trunkHelperFactory{}, trunkStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, trunkHelperFactory{}, trunkStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go index e0f106927..86e34b4b1 100644 --- a/internal/controllers/user/controller.go +++ b/internal/controllers/user/controller.go @@ -19,6 +19,7 @@ package user import ( "context" "errors" + "time" corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" @@ -41,17 +42,22 @@ const controllerName = "user" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=users/status,verbs=get;update;patch type userReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return userReconcilerConstructor{scopeFactory: scopeFactory} + return &userReconcilerConstructor{scopeFactory: scopeFactory} } func (userReconcilerConstructor) GetName() string { return controllerName } +func (c *userReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.UserList, *orcv1alpha1.Domain]( "spec.resource.domainRef", func(user *orcv1alpha1.User) []string { @@ -99,7 +105,7 @@ var passwordDependency = dependency.NewDependency[*orcv1alpha1.UserList, *corev1 ) // SetupWithManager sets up the controller with the Manager. -func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -156,6 +162,6 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, userHelperFactory{}, userStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, userHelperFactory{}, userStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/volume/controller.go b/internal/controllers/volume/controller.go index 1438d422e..8c0d254ff 100644 --- a/internal/controllers/volume/controller.go +++ b/internal/controllers/volume/controller.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -51,17 +52,22 @@ const controllerName = "volume" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=volumes/status,verbs=get;update;patch type volumeReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return volumeReconcilerConstructor{scopeFactory: scopeFactory} + return &volumeReconcilerConstructor{scopeFactory: scopeFactory} } func (volumeReconcilerConstructor) GetName() string { return controllerName } +func (c *volumeReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var volumetypeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.VolumeList, *orcv1alpha1.VolumeType]( "spec.resource.volumeTypeRef", func(volume *orcv1alpha1.Volume) []string { @@ -219,7 +225,7 @@ func serverToVolumeMapFunc(ctx context.Context, k8sClient client.Client) handler } // SetupWithManager sets up the controller with the Manager. -func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) k8sClient := mgr.GetClient() @@ -255,6 +261,6 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, volumeHelperFactory{}, volumeStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, volumeHelperFactory{}, volumeStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/controllers/volumetype/controller.go b/internal/controllers/volumetype/controller.go index 45707166a..a358a6d2d 100644 --- a/internal/controllers/volumetype/controller.go +++ b/internal/controllers/volumetype/controller.go @@ -19,6 +19,7 @@ package volumetype import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -36,19 +37,24 @@ const controllerName = "volumetype" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=volumetypes/status,verbs=get;update;patch type volumetypeReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return volumetypeReconcilerConstructor{scopeFactory: scopeFactory} + return &volumetypeReconcilerConstructor{scopeFactory: scopeFactory} } func (volumetypeReconcilerConstructor) GetName() string { return controllerName } +func (c *volumetypeReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // SetupWithManager sets up the controller with the Manager. -func (c volumetypeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *volumetypeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := ctrl.LoggerFrom(ctx) builder := ctrl.NewControllerManagedBy(mgr). @@ -62,6 +68,6 @@ func (c volumetypeReconcilerConstructor) SetupWithManager(ctx context.Context, m return err } - r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, volumetypeHelperFactory{}, volumetypeStatusWriter{}) + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, volumetypeHelperFactory{}, volumetypeStatusWriter{}, c.defaultResyncPeriod) return builder.Complete(&r) } diff --git a/internal/manager/manager.go b/internal/manager/manager.go index ea84eb77f..e8d5932c9 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -20,6 +20,7 @@ import ( "context" "crypto/tls" "fmt" + "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. @@ -49,8 +50,11 @@ type Options struct { TLSOpts []func(*tls.Config) ScopeCacheMaxSize int WatchNamespaces []string + DefaultResyncPeriod time.Duration } +const lowDefaultResyncPeriodWarningThreshold = 2 * time.Minute + func Run(ctx context.Context, opts *Options, restConfig *rest.Config, scheme *runtime.Scheme, setupLog, log logr.Logger, controllers []interfaces.Controller) error { // if the enable-http2 flag is false (the default), http/2 should be disabled // due to its vulnerabilities. More specifically, disabling http/2 will @@ -141,7 +145,14 @@ func Run(ctx context.Context, opts *Options, restConfig *rest.Config, scheme *ru return fmt.Errorf("unable to set up ready check: %w", err) } + if opts.DefaultResyncPeriod > 0 && opts.DefaultResyncPeriod < lowDefaultResyncPeriodWarningThreshold { + setupLog.Info("warning: default resync period is very low and may cause excessive OpenStack API load", + "defaultResyncPeriod", opts.DefaultResyncPeriod.String(), + "recommendedMinimum", lowDefaultResyncPeriodWarningThreshold.String()) + } + for _, c := range controllers { + c.SetDefaultResyncPeriod(opts.DefaultResyncPeriod) if err := c.SetupWithManager(ctx, mgr, controller.Options{}); err != nil { return fmt.Errorf("unable to create %s controller: %w", c.GetName(), err) } From 7c346b288d446676ae17ffa36a869746528fa607 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 8 Jul 2026 17:22:30 +0300 Subject: [PATCH 229/237] Handle external deletion as explicit reconcile state Represent externally deleted OpenStack resources with a typed ReconcileStatus instead of overloading ordinary errors. Managed resources can then clear status.id and re-enter the normal creation path, while unmanaged or imported resources keep terminal error behavior. Update the generic get-or-create and condition handling paths so external deletion is explicit, status updates remain coherent, and invalid actuator responses are surfaced as errors. --- .../generic/progress/reconcile_status.go | 31 ++++++++++++++++++- .../generic/reconciler/resource_actions.go | 16 ++++++++-- .../controllers/generic/status/conditions.go | 9 ++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/internal/controllers/generic/progress/reconcile_status.go b/internal/controllers/generic/progress/reconcile_status.go index 9b9b61c99..8277d6fea 100644 --- a/internal/controllers/generic/progress/reconcile_status.go +++ b/internal/controllers/generic/progress/reconcile_status.go @@ -42,6 +42,8 @@ type reconcileStatus struct { requeue time.Duration err error + + externallyDeleted bool } // NewReconcileStatus returns an empty ReconcileStatus @@ -177,9 +179,36 @@ func (r ReconcileStatus) WithReconcileStatus(o ReconcileStatus) ReconcileStatus return o } - return r.WithProgressMessage(o.GetProgressMessages()...). + r = r.WithProgressMessage(o.GetProgressMessages()...). WithRequeue(o.GetRequeue()). WithError(o.GetError()) + r.externallyDeleted = r.IsExternallyDeleted() || o.IsExternallyDeleted() + return r +} + +// ExternallyDeleted returns a ReconcileStatus indicating that the OpenStack +// resource referenced by status.id has been deleted outside of ORC. The caller +// is expected to clear status.id and add an appropriate progress message. +func (r ReconcileStatus) ExternallyDeleted() ReconcileStatus { + if r == nil { + r = &reconcileStatus{} + } + r.externallyDeleted = true + return r +} + +// ExternallyDeleted is a convenience method which returns a new ReconcileStatus with ExternallyDeleted. +func ExternallyDeleted() ReconcileStatus { + return NewReconcileStatus().ExternallyDeleted() +} + +// IsExternallyDeleted returns true if the ReconcileStatus indicates that the +// OpenStack resource was deleted externally. +func (r ReconcileStatus) IsExternallyDeleted() bool { + if r == nil { + return false + } + return r.externallyDeleted } // WaitingOnEvent represents the type of event we are waiting on diff --git a/internal/controllers/generic/reconciler/resource_actions.go b/internal/controllers/generic/reconciler/resource_actions.go index 49f4598b1..62aabad7c 100644 --- a/internal/controllers/generic/reconciler/resource_actions.go +++ b/internal/controllers/generic/reconciler/resource_actions.go @@ -70,16 +70,26 @@ func GetOrCreateOSResource[ osResource, reconcileStatus := actuator.GetOSResourceByID(ctx, *resourceID) if needsReschedule, err := reconcileStatus.NeedsReschedule(); needsReschedule { if orcerrors.IsNotFound(err) { - // An OpenStack resource we previously referenced has been deleted unexpectedly. We can't recover from this. + // The OpenStack resource referenced by status.id no longer exists. + // For managed resources we trigger recreation by returning a typed + // signal: the caller will clear status.id and re-enter the creation + // path on the next reconcile. + // For unmanaged resources we cannot recreate them, so we return a + // terminal error. + if objAdapter.GetManagementPolicy() == orcv1alpha1.ManagementPolicyManaged { + log.V(logging.Info).Info("OpenStack resource was deleted externally; will signal caller to clear status ID and trigger recreation") + return nil, progress.ExternallyDeleted() + } return osResource, progress.WrapError( orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "resource has been deleted from OpenStack")) } else { return osResource, reconcileStatus } } - if osResource != nil { - log.V(logging.Verbose).Info("Got existing OpenStack resource", "ID", actuator.GetResourceID(osResource)) + if osResource == nil { + return nil, progress.WrapError(fmt.Errorf("GetOSResourceByID returned nil resource with no error for ID %q", *resourceID)) } + log.V(logging.Verbose).Info("Got existing OpenStack resource", "ID", actuator.GetResourceID(osResource)) return osResource, nil } diff --git a/internal/controllers/generic/status/conditions.go b/internal/controllers/generic/status/conditions.go index 9ac683150..b154509f1 100644 --- a/internal/controllers/generic/status/conditions.go +++ b/internal/controllers/generic/status/conditions.go @@ -40,6 +40,15 @@ func SetCommonConditions[T any]( reconcileStatus progress.ReconcileStatus, now metav1.Time, ) { + // Terminal errors make the resource definitively unavailable. + // Override Unknown → False so Available matches the error severity. + if availableStatus != metav1.ConditionTrue { + var terminalErr *orcerrors.TerminalError + if errors.As(reconcileStatus.GetError(), &terminalErr) { + availableStatus = metav1.ConditionFalse + } + } + availableCondition := applyconfigv1.Condition(). WithType(orcv1alpha1.ConditionAvailable). WithStatus(availableStatus). From c77393d52715c2cb4a56f4f090092c5d20878e30 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 8 Jul 2026 17:22:55 +0300 Subject: [PATCH 230/237] Support resync for relationship controllers Extend the resync behavior to custom relationship controllers that do not follow the generated generic controller path. RoleAssignment can now periodically verify and recreate missing assignments, and RouterInterface gains matching resync and lastSyncTime handling. Reuse the generic ShouldReconcile predicate to keep skip/resync decisions consistent across generic controllers, RoleAssignment, and RouterInterface while preserving relationship-specific reconciliation flow. --- .../controllers/roleassignment/controller.go | 15 +- .../controllers/roleassignment/reconciler.go | 155 +++++++++--------- .../controllers/routerinterface/controller.go | 21 ++- .../controllers/routerinterface/reconcile.go | 32 +++- .../controllers/routerinterface/status.go | 3 + 5 files changed, 135 insertions(+), 91 deletions(-) diff --git a/internal/controllers/roleassignment/controller.go b/internal/controllers/roleassignment/controller.go index 9051fc97f..24409089d 100644 --- a/internal/controllers/roleassignment/controller.go +++ b/internal/controllers/roleassignment/controller.go @@ -19,6 +19,7 @@ package roleassignment import ( "context" "errors" + "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -39,17 +40,22 @@ const controllerName = "roleassignment" // +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roleassignments/status,verbs=get;update;patch type roleassignmentReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return roleassignmentReconcilerConstructor{scopeFactory: scopeFactory} + return &roleassignmentReconcilerConstructor{scopeFactory: scopeFactory} } func (roleassignmentReconcilerConstructor) GetName() string { return controllerName } +func (c *roleassignmentReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + var roleDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Role]( "spec.resource.roleRef", func(roleassignment *orcv1alpha1.RoleAssignment) []string { @@ -278,8 +284,9 @@ func (c roleassignmentReconcilerConstructor) SetupWithManager(ctx context.Contex // Custom reconciler for role assignments (relationships, not resources with IDs) reconciler := &roleassignmentReconciler{ - client: mgr.GetClient(), - scopeFactory: c.scopeFactory, + client: mgr.GetClient(), + scopeFactory: c.scopeFactory, + defaultResyncPeriod: c.defaultResyncPeriod, } return builder.Complete(reconciler) } diff --git a/internal/controllers/roleassignment/reconciler.go b/internal/controllers/roleassignment/reconciler.go index 2e2bd5ba0..17c8f8849 100644 --- a/internal/controllers/roleassignment/reconciler.go +++ b/internal/controllers/roleassignment/reconciler.go @@ -24,14 +24,14 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/resync" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/status" "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/scope" @@ -49,8 +49,9 @@ const ( // Unlike other ORC resources, role assignments are relationships (not resources with IDs), // so this uses a custom reconciler instead of the generic framework. type roleassignmentReconciler struct { - client client.Client - scopeFactory scope.Factory + client client.Client + scopeFactory scope.Factory + defaultResyncPeriod time.Duration statusWriter roleassignmentStatusWriter } @@ -82,22 +83,11 @@ func (r *roleassignmentReconciler) Reconcile(ctx context.Context, req ctrl.Reque return r.reconcileNormal(ctx, orcObject).Return(log) } -// shouldReconcile determines if reconciliation should proceed based on the Progressing condition. -// Returns true if: -// - Progressing condition is not present -// - Progressing condition is True -// - Progressing condition is False but observedGeneration is stale -func shouldReconcile(obj orcObjectPT) bool { - progressing := meta.FindStatusCondition(obj.GetConditions(), orcv1alpha1.ConditionProgressing) - if progressing == nil { - return true - } - - if progressing.Status == metav1.ConditionTrue { - return true - } - - return progressing.ObservedGeneration != obj.GetGeneration() +func hasRoleAssignmentComponents(statusResource *orcv1alpha1.RoleAssignmentResourceStatus) bool { + return statusResource != nil && + statusResource.RoleID != "" && + (statusResource.UserID != "" || statusResource.GroupID != "") && + (statusResource.ProjectID != "" || statusResource.DomainID != "") } // reconcileNormal handles the normal reconciliation flow: @@ -107,10 +97,14 @@ func shouldReconcile(obj orcObjectPT) bool { // 4. Update status func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObject orcObjectPT) (reconcileStatus progress.ReconcileStatus) { log := ctrl.LoggerFrom(ctx) + effectiveResyncPeriod := resync.DetermineResyncPeriod(orcObject.Spec.ResyncPeriod, r.defaultResyncPeriod) // Check if we should skip reconciliation - if !shouldReconcile(orcObject) { + if !reconciler.ShouldReconcile(orcObject, orcObject.Status.LastSyncTime, effectiveResyncPeriod) { log.V(logging.Verbose).Info("Status is up to date: not reconciling") + if remaining := resync.RemainingUntilNextSync(orcObject.Status.LastSyncTime, effectiveResyncPeriod); remaining > 0 { + return reconcileStatus.WithRequeue(remaining) + } return reconcileStatus } @@ -145,10 +139,7 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec if orcObject.Status.Resource != nil { statusResource := orcObject.Status.Resource // If we have all components in status, try to fetch the role assignment - if statusResource.RoleID != "" && - (statusResource.UserID != "" || statusResource.GroupID != "") && - (statusResource.ProjectID != "" || statusResource.DomainID != "") { - + if hasRoleAssignmentComponents(statusResource) { osResource, getRS := actuator.GetResourceByComponents( ctx, statusResource.RoleID, @@ -163,81 +154,91 @@ func (r *roleassignmentReconciler) reconcileNormal(ctx context.Context, orcObjec if osResource != nil { log.V(logging.Verbose).Info("Got existing role assignment") - return reconcileStatus + } else { + // Status was fully populated but the resource no longer exists in + // OpenStack. GetResourceByComponents uses a LIST query which returns + // (nil, nil) for empty results rather than a 404 error, so we detect + // deletion here. + if orcObject.Spec.ManagementPolicy == orcv1alpha1.ManagementPolicyUnmanaged { + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "role assignment has been deleted from OpenStack")) + } + log.V(logging.Info).Info("Role assignment was deleted externally; will recreate") } - - // Status was fully populated but the resource no longer - // exists in OpenStack. GetResourceByComponents uses a - // LIST query which returns (nil, nil) for empty results - // rather than a 404 error, so we detect deletion here. - return progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "role assignment has been deleted from OpenStack")) } } // Phase 5: Import by filter - if importSpec := orcObject.Spec.Import; importSpec != nil { - if filter := importSpec.Filter; filter != nil { - resourceIter, importRS := actuator.ListOSResourcesForImport(ctx, orcObject, *filter) - if needsReschedule, _ := importRS.NeedsReschedule(); needsReschedule { - return importRS.WithReconcileStatus(reconcileStatus) + if osResource == nil { + if importSpec := orcObject.Spec.Import; importSpec != nil { + if filter := importSpec.Filter; filter != nil { + resourceIter, importRS := actuator.ListOSResourcesForImport(ctx, orcObject, *filter) + if needsReschedule, _ := importRS.NeedsReschedule(); needsReschedule { + return importRS.WithReconcileStatus(reconcileStatus) + } + + var err error + osResource, err = atMostOne(resourceIter, + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, + "found more than one matching OpenStack resource during import")) + if err != nil { + return progress.WrapError(err) + } + + if osResource == nil { + return progress.WaitingOnOpenStack(progress.WaitingOnCreation, externalUpdatePollingPeriod) + } + + log.V(logging.Info).Info("Imported role assignment") } + } + } + // Phase 6: Adoption - check for existing resource before creating + if osResource == nil { + if orcObject.Spec.ManagementPolicy == orcv1alpha1.ManagementPolicyUnmanaged { + // We never create an unmanaged resource + // API validation should have ensured that one of the above functions returned + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Not creating unmanaged resource")) + } + + if resourceIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject); canAdopt { var err error osResource, err = atMostOne(resourceIter, orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "found more than one matching OpenStack resource during import")) + "found more than one matching OpenStack resource during adoption")) if err != nil { return progress.WrapError(err) } - - if osResource == nil { - return progress.WaitingOnOpenStack(progress.WaitingOnCreation, externalUpdatePollingPeriod) + if osResource != nil { + log.V(logging.Info).Info("Adopted previously created resource") } - - log.V(logging.Info).Info("Imported role assignment") - return reconcileStatus } } - // Phase 6: Adoption - check for existing resource before creating - if orcObject.Spec.ManagementPolicy == orcv1alpha1.ManagementPolicyUnmanaged { - // We never create an unmanaged resource - // API validation should have ensured that one of the above functions returned - return progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Not creating unmanaged resource")) - } - - resourceIter, canAdopt := actuator.ListOSResourcesForAdoption(ctx, orcObject) - if canAdopt { - var err error - osResource, err = atMostOne(resourceIter, - orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, - "found more than one matching OpenStack resource during adoption")) - if err != nil { - return progress.WrapError(err) - } - if osResource != nil { - log.V(logging.Info).Info("Adopted previously created resource") - return reconcileStatus + // Phase 7: Fetch dependencies and create role assignment + if osResource == nil { + log.V(logging.Info).Info("Creating resource") + var createRS progress.ReconcileStatus + osResource, createRS = actuator.CreateResource(ctx, orcObject) + if needsReschedule, err := createRS.NeedsReschedule(); needsReschedule { + if err == nil { + log.V(logging.Verbose).Info("Waiting on dependencies or creation") + } + return createRS.WithReconcileStatus(reconcileStatus) } - } - // Phase 7: Fetch dependencies and create role assignment - log.V(logging.Info).Info("Creating resource") - osResource, createRS := actuator.CreateResource(ctx, orcObject) - if needsReschedule, err := createRS.NeedsReschedule(); needsReschedule { - if err == nil { - log.V(logging.Verbose).Info("Waiting on dependencies or creation") + if osResource == nil { + return reconcileStatus.WithError(fmt.Errorf("osResource is not set, but no wait events or error")) } - return createRS.WithReconcileStatus(reconcileStatus) - } - if osResource == nil { - return reconcileStatus.WithError(fmt.Errorf("osResource is not set, but no wait events or error")) + log.V(logging.Info).Info("Role assignment created") } - log.V(logging.Info).Info("Role assignment created or adopted") + if resync.ShouldScheduleResync(effectiveResyncPeriod, reconcileStatus) { + reconcileStatus = reconcileStatus.WithRequeue(resync.CalculateJitteredDuration(effectiveResyncPeriod)) + } return reconcileStatus } diff --git a/internal/controllers/routerinterface/controller.go b/internal/controllers/routerinterface/controller.go index b25cdacf9..62c066d92 100644 --- a/internal/controllers/routerinterface/controller.go +++ b/internal/controllers/routerinterface/controller.go @@ -45,21 +45,27 @@ const ( ) type routerInterfaceReconcilerConstructor struct { - scopeFactory scope.Factory + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } func New(scopeFactory scope.Factory) interfaces.Controller { - return routerInterfaceReconcilerConstructor{scopeFactory: scopeFactory} + return &routerInterfaceReconcilerConstructor{scopeFactory: scopeFactory} } func (routerInterfaceReconcilerConstructor) GetName() string { return controllerName } +func (c *routerInterfaceReconcilerConstructor) SetDefaultResyncPeriod(d time.Duration) { + c.defaultResyncPeriod = d +} + // orcRouterInterfaceReconciler reconciles an ORC Subnet. type orcRouterInterfaceReconciler struct { - client client.Client - scopeFactory scope.Factory + client client.Client + scopeFactory scope.Factory + defaultResyncPeriod time.Duration } const controllerName = "routerinterface" @@ -89,7 +95,7 @@ var ( ) // SetupWithManager sets up the controller with the Manager. -func (c routerInterfaceReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { +func (c *routerInterfaceReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { log := mgr.GetLogger().WithValues("controller", controllerName) if err := errors.Join( @@ -105,8 +111,9 @@ func (c routerInterfaceReconcilerConstructor) SetupWithManager(ctx context.Conte // dependencies because it reconciles Routers, not RouterInterfaces. reconciler := orcRouterInterfaceReconciler{ - client: k8sClient, - scopeFactory: c.scopeFactory, + client: k8sClient, + scopeFactory: c.scopeFactory, + defaultResyncPeriod: c.defaultResyncPeriod, } return ctrl.NewControllerManagedBy(mgr). For(&orcv1alpha1.Router{}, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Router{}))). diff --git a/internal/controllers/routerinterface/reconcile.go b/internal/controllers/routerinterface/reconcile.go index 6b8daeb03..61a950410 100644 --- a/internal/controllers/routerinterface/reconcile.go +++ b/internal/controllers/routerinterface/reconcile.go @@ -31,6 +31,8 @@ import ( orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/resync" "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" @@ -101,6 +103,32 @@ func (r *orcRouterInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.R return ctrl.Result{}, nil } + var reconcileStatus progress.ReconcileStatus + routerInterfacesToReconcile := make([]*orcv1alpha1.RouterInterface, 0, len(routerInterfaces)) + for i := range routerInterfaces { + routerInterface := &routerInterfaces[i] + + if !routerInterface.GetDeletionTimestamp().IsZero() { + routerInterfacesToReconcile = append(routerInterfacesToReconcile, routerInterface) + continue + } + + effectiveResyncPeriod := resync.DetermineResyncPeriod(routerInterface.Spec.ResyncPeriod, r.defaultResyncPeriod) + if !reconciler.ShouldReconcile(routerInterface, routerInterface.Status.LastSyncTime, effectiveResyncPeriod) { + if remaining := resync.RemainingUntilNextSync(routerInterface.Status.LastSyncTime, effectiveResyncPeriod); remaining > 0 { + reconcileStatus = reconcileStatus.WithRequeue(remaining) + } + continue + } + + routerInterfacesToReconcile = append(routerInterfacesToReconcile, routerInterface) + } + + if len(routerInterfacesToReconcile) == 0 { + log.V(logging.Verbose).Info("Router interfaces are up to date: not reconciling") + return reconcileStatus.Return(log) + } + // If there are interfaces, the router should have our finalizer if err := dependency.EnsureFinalizer(ctx, r.client, router, finalizer, fieldOwner); err != nil { return ctrl.Result{}, fmt.Errorf("writing finalizer: %w", err) @@ -134,9 +162,7 @@ func (r *orcRouterInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.R } } - var reconcileStatus progress.ReconcileStatus - for i := range routerInterfaces { - routerInterface := &routerInterfaces[i] + for _, routerInterface := range routerInterfacesToReconcile { log = log.WithValues("name", routerInterface.Name) var ifReconcileStatus progress.ReconcileStatus diff --git a/internal/controllers/routerinterface/status.go b/internal/controllers/routerinterface/status.go index 6c3ea10e8..971168908 100644 --- a/internal/controllers/routerinterface/status.go +++ b/internal/controllers/routerinterface/status.go @@ -64,6 +64,9 @@ func createStatusUpdate(orcObject *orcv1alpha1.RouterInterface, port *osclients. isAvailable, statusReconcileStatus := getStatusSummary(port) reconcileStatus = reconcileStatus.WithReconcileStatus(statusReconcileStatus) status.SetCommonConditions(orcObject, applyConfigStatus, isAvailable, reconcileStatus, now) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); !needsReschedule { + applyConfigStatus.WithLastSyncTime(now) + } return applyConfig, reconcileStatus } From 41dcdaa79d987d96683e51f86a31d28886e66ea5 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 8 Jul 2026 17:23:51 +0300 Subject: [PATCH 231/237] Add resync and external deletion coverage Add focused unit coverage for resync period resolution, jittered scheduling, lastSyncTime updates, external deletion state propagation, and generic reconcile skip behavior. Add API validation and KUTTL scenarios covering RouterInterface resync mutability, network resync scheduling, disabled resync behavior, terminal-error exclusion, and managed/imported external deletion outcomes. --- .../generic/reconciler/controller_test.go | 483 ++++++++++++++++ .../reconciler/resource_actions_test.go | 119 ++++ .../resource_actions_unmanaged_test.go | 516 ++++++++++++++++++ .../controllers/generic/resync/config_test.go | 125 +++++ .../generic/resync/scheduler_test.go | 216 ++++++++ .../generic/status/conditions_test.go | 155 ++++++ .../controllers/generic/status/status_test.go | 228 ++++++++ .../00-assert.yaml | 14 + .../00-create-external.yaml | 15 + .../00-secret.yaml | 5 + .../01-assert.yaml | 22 + .../01-import-resource.yaml | 19 + .../02-assert.yaml | 28 + .../02-delete-from-openstack.yaml | 23 + .../README.md | 27 + .../network-external-deletion/00-assert.yaml | 41 ++ .../00-create-resource.yaml | 18 + .../network-external-deletion/00-secret.yaml | 5 + .../network-external-deletion/01-assert.yaml | 50 ++ .../01-delete-from-openstack.yaml | 16 + .../tests/network-external-deletion/README.md | 29 + .../network-resync-disabled/00-assert.yaml | 33 ++ .../00-create-resource.yaml | 13 + .../network-resync-disabled/00-secret.yaml | 5 + .../network-resync-disabled/01-assert.yaml | 15 + .../01-check-no-resync.yaml | 17 + .../tests/network-resync-disabled/README.md | 21 + .../network-resync-jitter/00-assert.yaml | 61 +++ .../00-create-resources.yaml | 39 ++ .../network-resync-jitter/00-secret.yaml | 5 + .../network-resync-jitter/01-assert.yaml | 39 ++ .../01-record-sync-times.yaml | 18 + .../tests/network-resync-jitter/README.md | 24 + .../00-assert.yaml | 26 + .../00-create-resources.yaml | 26 + .../00-secret.yaml | 5 + .../01-assert.yaml | 29 + .../01-import-resource.yaml | 20 + .../02-assert.yaml | 29 + .../02-check-no-resync.yaml | 9 + .../network-resync-terminal-error/README.md | 30 + test/apivalidations/routerinterface_test.go | 25 +- 42 files changed, 2640 insertions(+), 3 deletions(-) create mode 100644 internal/controllers/generic/reconciler/controller_test.go create mode 100644 internal/controllers/generic/reconciler/resource_actions_test.go create mode 100644 internal/controllers/generic/reconciler/resource_actions_unmanaged_test.go create mode 100644 internal/controllers/generic/resync/config_test.go create mode 100644 internal/controllers/generic/resync/scheduler_test.go create mode 100644 internal/controllers/generic/status/conditions_test.go create mode 100644 internal/controllers/generic/status/status_test.go create mode 100644 internal/controllers/network/tests/network-external-deletion-import/00-assert.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/00-create-external.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/00-secret.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/01-assert.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/01-import-resource.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/02-assert.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/02-delete-from-openstack.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion-import/README.md create mode 100644 internal/controllers/network/tests/network-external-deletion/00-assert.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion/00-create-resource.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion/00-secret.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion/01-assert.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion/01-delete-from-openstack.yaml create mode 100644 internal/controllers/network/tests/network-external-deletion/README.md create mode 100644 internal/controllers/network/tests/network-resync-disabled/00-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-disabled/00-create-resource.yaml create mode 100644 internal/controllers/network/tests/network-resync-disabled/00-secret.yaml create mode 100644 internal/controllers/network/tests/network-resync-disabled/01-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-disabled/01-check-no-resync.yaml create mode 100644 internal/controllers/network/tests/network-resync-disabled/README.md create mode 100644 internal/controllers/network/tests/network-resync-jitter/00-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-jitter/00-create-resources.yaml create mode 100644 internal/controllers/network/tests/network-resync-jitter/00-secret.yaml create mode 100644 internal/controllers/network/tests/network-resync-jitter/01-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-jitter/01-record-sync-times.yaml create mode 100644 internal/controllers/network/tests/network-resync-jitter/README.md create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/00-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/00-create-resources.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/00-secret.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/01-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/01-import-resource.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/02-assert.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/02-check-no-resync.yaml create mode 100644 internal/controllers/network/tests/network-resync-terminal-error/README.md diff --git a/internal/controllers/generic/reconciler/controller_test.go b/internal/controllers/generic/reconciler/controller_test.go new file mode 100644 index 000000000..d0bf80430 --- /dev/null +++ b/internal/controllers/generic/reconciler/controller_test.go @@ -0,0 +1,483 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 reconciler + +import ( + "testing" + "time" + + 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/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/resync" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// makeObj creates a Flavor object with the given generation and conditions, +// satisfying orcv1alpha1.ObjectWithConditions. +func makeObj(generation int64, conditions []metav1.Condition) orcv1alpha1.ObjectWithConditions { + f := &orcv1alpha1.Flavor{} + f.Generation = generation + f.Status.Conditions = conditions + return f +} + +// makeProgressingCondition returns a Progressing condition with the given +// status and observedGeneration. +func makeProgressingCondition(status metav1.ConditionStatus, observedGeneration int64) metav1.Condition { //nolint:unparam + return metav1.Condition{ + Type: orcv1alpha1.ConditionProgressing, + Status: status, + ObservedGeneration: observedGeneration, + Reason: "Test", + } +} + +// agoPtr returns a *metav1.Time that is d in the past. +func agoPtr(d time.Duration) *metav1.Time { + t := metav1.NewTime(time.Now().Add(-d)) + return &t +} + +// nowPtr returns a *metav1.Time set to approximately now. +func nowPtr() *metav1.Time { + t := metav1.Now() + return &t +} + +func TestShouldReconcile_ConditionBased(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + generation int64 + conditions []metav1.Condition + lastSyncTime *metav1.Time + resyncPeriod time.Duration + want bool + }{ + { + name: "no conditions: should reconcile", + generation: 1, + conditions: nil, + want: true, + }, + { + name: "Progressing=True up-to-date: should reconcile", + generation: 1, + conditions: []metav1.Condition{ + makeProgressingCondition(metav1.ConditionTrue, 1), + }, + want: true, + }, + { + name: "Progressing=False up-to-date resync disabled: should not reconcile", + generation: 1, + conditions: []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }, + resyncPeriod: 0, + want: false, + }, + { + name: "Progressing=False stale generation: should reconcile", + generation: 2, + conditions: []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }, + want: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + obj := makeObj(tc.generation, tc.conditions) + got := ShouldReconcile(obj, tc.lastSyncTime, tc.resyncPeriod) + if got != tc.want { + t.Errorf("ShouldReconcile() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestShouldReconcile_ResyncDisabled(t *testing.T) { + t.Parallel() + + // An up-to-date Progressing=False condition prevents reconciliation when + // resync is disabled (resyncPeriod <= 0). + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }) + + tests := []struct { + name string + resyncPeriod time.Duration + lastSyncTime *metav1.Time + }{ + { + name: "resyncPeriod=0 nil lastSyncTime", + resyncPeriod: 0, + lastSyncTime: nil, + }, + { + name: "resyncPeriod=0 old lastSyncTime", + resyncPeriod: 0, + lastSyncTime: agoPtr(24 * time.Hour), + }, + { + name: "negative resyncPeriod", + resyncPeriod: -1 * time.Minute, + lastSyncTime: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := ShouldReconcile(obj, tc.lastSyncTime, tc.resyncPeriod) + if got { + t.Errorf("ShouldReconcile() = true; want false when resync disabled (resyncPeriod=%v)", tc.resyncPeriod) + } + }) + } +} + +func TestShouldReconcile_ResyncEnabled_NilLastSyncTime(t *testing.T) { + t.Parallel() + + // When resyncPeriod > 0 and lastSyncTime is nil (never synced), reconcile + // immediately (persisted time is absent → treat as overdue). + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }) + + got := ShouldReconcile(obj, nil, 10*time.Minute) + if !got { + t.Error("ShouldReconcile() = false; want true when lastSyncTime is nil and resyncPeriod > 0") + } +} + +func TestShouldReconcile_ResyncEnabled_PeriodElapsed(t *testing.T) { + t.Parallel() + + // When time.Since(lastSyncTime) >= resyncPeriod, a resync is due. + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }) + + // Last synced 20 minutes ago, period is 10 minutes. + got := ShouldReconcile(obj, agoPtr(20*time.Minute), 10*time.Minute) + if !got { + t.Error("ShouldReconcile() = false; want true when time.Since(lastSyncTime) >= resyncPeriod") + } +} + +func TestShouldReconcile_ResyncEnabled_PeriodNotElapsed(t *testing.T) { + t.Parallel() + + // When time.Since(lastSyncTime) < resyncPeriod, no resync is due. + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }) + + // Last synced 2 minutes ago, period is 10 minutes. + got := ShouldReconcile(obj, agoPtr(2*time.Minute), 10*time.Minute) + if got { + t.Error("ShouldReconcile() = true; want false when time.Since(lastSyncTime) < resyncPeriod") + } +} + +func TestShouldReconcile_ResyncEnabled_JustPastPeriod(t *testing.T) { + t.Parallel() + + // Boundary condition: just past the period should trigger resync (>= semantics). + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }) + + resyncPeriod := 10 * time.Minute + // Add a small extra to ensure we're past the boundary even accounting for + // time elapsed during test execution. + lastSyncTime := agoPtr(resyncPeriod + 100*time.Millisecond) + + got := ShouldReconcile(obj, lastSyncTime, resyncPeriod) + if !got { + t.Error("ShouldReconcile() = false; want true when time.Since(lastSyncTime) is just past resyncPeriod") + } +} + +func TestShouldReconcile_ResyncEnabled_ProgressingTrue_IgnoresResyncNotElapsed(t *testing.T) { + t.Parallel() + + // Progressing=True always triggers reconciliation even if resync period has + // not elapsed yet (condition-based logic takes priority for positive cases). + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionTrue, 1), + }) + + // lastSyncTime is very recent, so resync would say "false". + // But Progressing=True means we must reconcile anyway. + got := ShouldReconcile(obj, nowPtr(), time.Hour) + if !got { + t.Error("ShouldReconcile() = false; want true when Progressing=True regardless of resync period") + } +} + +func TestShouldReconcile_ResyncEnabled_ControllerRestart_PersistsLastSyncTime(t *testing.T) { + t.Parallel() + + // Thundering-herd prevention: after a controller restart, + // lastSyncTime is read from the persisted Kubernetes status. If the + // persisted time is recent, ShouldReconcile should return false so the + // controller does not immediately hammer OpenStack for all resources at once. + obj := makeObj(1, []metav1.Condition{ + makeProgressingCondition(metav1.ConditionFalse, 1), + }) + + resyncPeriod := 30 * time.Minute + // Simulated: last sync was 5 minutes ago (persisted from before restart). + lastSyncTime := agoPtr(5 * time.Minute) + + got := ShouldReconcile(obj, lastSyncTime, resyncPeriod) + if got { + t.Error("ShouldReconcile() = true; want false: controller should respect persisted lastSyncTime after restart") + } +} + +func TestShouldReconcile_ExistingBehaviorUnchanged_ResyncPeriodZero(t *testing.T) { + t.Parallel() + + // When resyncPeriod is 0 (disabled), ShouldReconcile behaves exactly as it + // did before the resync feature was added: only condition-based logic applies. + tests := []struct { + name string + generation int64 + conditions []metav1.Condition + want bool + }{ + { + name: "no conditions", + generation: 1, + want: true, + }, + { + name: "progressing true", + generation: 1, + conditions: []metav1.Condition{makeProgressingCondition(metav1.ConditionTrue, 1)}, + want: true, + }, + { + name: "progressing false up-to-date", + generation: 1, + conditions: []metav1.Condition{makeProgressingCondition(metav1.ConditionFalse, 1)}, + want: false, + }, + { + name: "progressing false stale", + generation: 2, + conditions: []metav1.Condition{makeProgressingCondition(metav1.ConditionFalse, 1)}, + want: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + obj := makeObj(tc.generation, tc.conditions) + // resyncPeriod=0 and nil lastSyncTime: pure condition-based behaviour. + got := ShouldReconcile(obj, nil, 0) + if got != tc.want { + t.Errorf("ShouldReconcile() = %v, want %v (existing behaviour should be unchanged)", got, tc.want) + } + }) + } +} + +// scheduleResyncRequeue simulates the resync scheduling logic added to the end +// of reconcileNormal: +// +// if resync.ShouldScheduleResync(effectiveResyncPeriod, reconcileStatus) { +// reconcileStatus = reconcileStatus.WithRequeue(resync.CalculateJitteredDuration(effectiveResyncPeriod)) +// } +// +// This helper allows the following tests to verify the combined behaviour of +// ShouldScheduleResync and CalculateJitteredDuration without requiring a full +// Kubernetes environment. +func scheduleResyncRequeue(reconcileStatus progress.ReconcileStatus, period time.Duration) progress.ReconcileStatus { + if resync.ShouldScheduleResync(period, reconcileStatus) { + reconcileStatus = reconcileStatus.WithRequeue(resync.CalculateJitteredDuration(period)) + } + return reconcileStatus +} + +// TestResyncRequeue_ScheduledWhenPeriodPositive verifies that a resync requeue +// is added to a clean ReconcileStatus when resyncPeriod > 0. +func TestResyncRequeue_ScheduledWhenPeriodPositive(t *testing.T) { + t.Parallel() + + const period = 10 * time.Minute + + // A clean (nil) ReconcileStatus represents a successful reconciliation with + // no errors and no pending requeue. + var rs progress.ReconcileStatus + rs = scheduleResyncRequeue(rs, period) + + requeue := rs.GetRequeue() + if requeue == 0 { + t.Fatal("expected a non-zero requeue duration after resync scheduling; got 0") + } + + // The requeue must be within the jitter range [period*1.0, period*1.2). + lo := time.Duration(float64(period) * 1.0) + hi := time.Duration(float64(period) * 1.2) + if requeue < lo || requeue > hi { + t.Errorf("resync requeue %v is outside jitter range [%v, %v]", requeue, lo, hi) + } +} + +// TestResyncRequeue_NotScheduledWhenPeriodZero verifies that no resync requeue +// is added when resyncPeriod is zero (disabled). +func TestResyncRequeue_NotScheduledWhenPeriodZero(t *testing.T) { + t.Parallel() + + var rs progress.ReconcileStatus + rs = scheduleResyncRequeue(rs, 0) + + if requeue := rs.GetRequeue(); requeue != 0 { + t.Errorf("expected no requeue when resyncPeriod=0; got %v", requeue) + } +} + +// TestResyncRequeue_NotScheduledWhenPeriodNegative verifies that no resync +// requeue is added when resyncPeriod is negative (effectively disabled). +func TestResyncRequeue_NotScheduledWhenPeriodNegative(t *testing.T) { + t.Parallel() + + var rs progress.ReconcileStatus + rs = scheduleResyncRequeue(rs, -1*time.Minute) + + if requeue := rs.GetRequeue(); requeue != 0 { + t.Errorf("expected no requeue when resyncPeriod<0; got %v", requeue) + } +} + +// TestResyncRequeue_NotScheduledWhenTerminalError verifies that no resync +// requeue is scheduled when the ReconcileStatus contains a terminal error. +// Terminal errors indicate the resource is in a non-retryable state; +// resyncing would be pointless and wasteful. +func TestResyncRequeue_NotScheduledWhenTerminalError(t *testing.T) { + t.Parallel() + + const period = 10 * time.Minute + + termErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid config", nil) + rs := progress.WrapError(termErr) + rs = scheduleResyncRequeue(rs, period) + + if requeue := rs.GetRequeue(); requeue != 0 { + t.Errorf("expected no resync requeue on terminal error; got %v", requeue) + } +} + +// TestResyncRequeue_NotScheduledWhenRequeueAlreadyPending verifies that no +// additional resync requeue is scheduled when one is already set. +// This prevents redundant requeues when the reconciler is already waiting on +// an OpenStack event or dependency. +func TestResyncRequeue_NotScheduledWhenRequeueAlreadyPending(t *testing.T) { + t.Parallel() + + const period = 10 * time.Minute + const existingRequeue = 5 * time.Second + + // Simulate a reconcile status that already has a short requeue (e.g., waiting + // for an OpenStack resource to become ready). + rs := progress.NewReconcileStatus().WithRequeue(existingRequeue) + rs = scheduleResyncRequeue(rs, period) + + // The existing requeue must be preserved unchanged; no extra requeue added. + if requeue := rs.GetRequeue(); requeue != existingRequeue { + t.Errorf("expected existing requeue %v to be preserved; got %v", existingRequeue, requeue) + } +} + +// TestResyncRequeue_JitterIsApplied verifies that multiple scheduling calls +// with the same period produce different requeue durations (jitter is random), +// and all values are within the expected [+0%, +20%] range. +func TestResyncRequeue_JitterIsApplied(t *testing.T) { + t.Parallel() + + const samples = 200 + period := time.Hour + + lo := time.Duration(float64(period) * 1.0) + hi := time.Duration(float64(period) * 1.2) + + unique := make(map[time.Duration]struct{}, samples) + for i := range samples { + var rs progress.ReconcileStatus + rs = scheduleResyncRequeue(rs, period) + d := rs.GetRequeue() + if d < lo || d > hi { + t.Errorf("sample %d: requeue %v outside jitter range [%v, %v]", i, d, lo, hi) + } + unique[d] = struct{}{} + } + + // With 200 samples from a continuous distribution, we expect nearly all + // values to be distinct. Require at least 90% uniqueness. + minUnique := samples * 9 / 10 + if len(unique) < minUnique { + t.Errorf("jitter appears non-random: only %d unique values out of %d samples (want >= %d)", len(unique), samples, minUnique) + } +} + +// TestResyncRequeue_RequeueTimingRange verifies the requeue timing over many +// samples remains within the [+0%, +20%] jitter window, functioning as an +// integration check of the scheduling logic used in reconcileNormal. +func TestResyncRequeue_RequeueTimingRange(t *testing.T) { + t.Parallel() + + periods := []time.Duration{ + time.Minute, + 10 * time.Minute, + time.Hour, + 24 * time.Hour, + } + + for _, period := range periods { + t.Run(period.String(), func(t *testing.T) { + t.Parallel() + + lo := time.Duration(float64(period) * 1.0) + hi := time.Duration(float64(period) * 1.2) + + for i := range 50 { + var rs progress.ReconcileStatus + rs = scheduleResyncRequeue(rs, period) + d := rs.GetRequeue() + if d < lo || d > hi { + t.Errorf("sample %d: period=%v requeue=%v outside [%v, %v]", + i, period, d, lo, hi) + } + } + }) + } +} diff --git a/internal/controllers/generic/reconciler/resource_actions_test.go b/internal/controllers/generic/reconciler/resource_actions_test.go new file mode 100644 index 000000000..5c83007e0 --- /dev/null +++ b/internal/controllers/generic/reconciler/resource_actions_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 reconciler contains unit tests for external deletion handling in +// GetOrCreateOSResource. +// +// These tests cover management policy behaviour when a resource's OpenStack +// counterpart is not found (404), verifying that: +// +// - Managed resources trigger recreation (IsExternallyDeleted) +// - Unmanaged resources return a terminal error +// - Managed, existing resources continue through the normal update flow +package reconciler + +import ( + "context" + "errors" + "testing" + + "github.com/go-logr/logr" + + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// -------------------------------------------------------------------------- +// External deletion tests — all use GetOrCreateOSResource directly. +// -------------------------------------------------------------------------- + +// TestGetOrCreateOSResource_ExternalDeletion_ManagedOrcCreated verifies that +// when a managed resource is externally deleted, +// GetOrCreateOSResource returns IsExternallyDeleted to signal the caller should +// clear status.ID and trigger recreation on the next reconcile. +func TestGetOrCreateOSResource_ExternalDeletion_ManagedOrcCreated(t *testing.T) { + t.Parallel() + + const resourceID = "orc-created-flavor-id" + + actuator := &noWriteActuator{t: t, readByIDErr: notFoundErr()} + adapter := managedFlavorWithStatusID(resourceID) + + got, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + if !rs.IsExternallyDeleted() { + t.Fatal("expected IsExternallyDeleted for externally-deleted managed resource") + } + if got != nil { + t.Errorf("expected nil osResource for recreation path, got %v", got) + } + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called: controller must attempt to fetch the resource") + } +} + +// TestGetOrCreateOSResource_ExternalDeletion_Unmanaged verifies that when an +// unmanaged resource is externally deleted (404), the controller returns a +// terminal error instead of calling CreateResource. +func TestGetOrCreateOSResource_ExternalDeletion_Unmanaged(t *testing.T) { + t.Parallel() + + const resourceID = "unmanaged-deleted-flavor-id" + + actuator := &noWriteActuator{t: t, readByIDErr: notFoundErr()} + adapter := unmanagedFlavorWithStatusID(resourceID) + + _, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + _, err := rs.NeedsReschedule() + if err == nil { + t.Fatal("expected a terminal error for externally-deleted unmanaged resource, got nil") + } + + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected a TerminalError for externally-deleted unmanaged resource, got %T: %v", err, err) + } + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called: controller must attempt to fetch the resource") + } +} + +// TestGetOrCreateOSResource_ExternalDeletion_ManagedResourceExists verifies +// the normal update flow: when a managed resource still exists in +// OpenStack, GetOrCreateOSResource returns the resource with a nil reconcile +// status so the caller proceeds with reconciliation (no recreation, no error). +func TestGetOrCreateOSResource_ExternalDeletion_ManagedResourceExists(t *testing.T) { + t.Parallel() + + const resourceID = "existing-managed-flavor-id" + osResource := &fakeOSResource{ID: resourceID} + + actuator := &noWriteActuator{t: t, readByIDResult: osResource} + adapter := managedFlavorWithStatusID(resourceID) + + got, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + needsReschedule, err := rs.NeedsReschedule() + if needsReschedule { + t.Fatalf("expected no rescheduling for existing resource, got needsReschedule=%v err=%v", needsReschedule, err) + } + if got == nil || got.ID != resourceID { + t.Errorf("expected osResource with ID=%q, got %v", resourceID, got) + } + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called") + } +} diff --git a/internal/controllers/generic/reconciler/resource_actions_unmanaged_test.go b/internal/controllers/generic/reconciler/resource_actions_unmanaged_test.go new file mode 100644 index 000000000..507256200 --- /dev/null +++ b/internal/controllers/generic/reconciler/resource_actions_unmanaged_test.go @@ -0,0 +1,516 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 reconciler contains integration tests verifying that unmanaged ORC +// resources do not invoke any OpenStack write operations during periodic resync. +// +// Acceptance criteria covered: +// - Unmanaged resources update status without invoking actuator updates. +// - Unmanaged resources still fetch current OpenStack state (read-only). +// - CreateResource is NEVER called for unmanaged resources. +// - Actuator reconcilers (GetResourceReconcilers) are NEVER called for +// unmanaged resources (this is enforced in reconcileNormal). +// +// The tests use thin mock types for the actuator so that any unexpected call to +// a write method (CreateResource) causes the test to fail immediately. +package reconciler + +import ( + "context" + "errors" + "iter" + "testing" + + "github.com/go-logr/logr" + "github.com/gophercloud/gophercloud/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/scope" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" +) + +// -------------------------------------------------------------------------- +// Minimal fake OpenStack resource type used in these tests. +// -------------------------------------------------------------------------- + +// fakeOSResource is a stand-in for any OpenStack resource (e.g. flavors.Flavor). +type fakeOSResource struct { + ID string +} + +// -------------------------------------------------------------------------- +// Mock actuator that enforces "no write operations". +// +// GetOSResourceByID and ListOSResourcesForImport are read-only operations and +// are expected to be called for unmanaged resources. CreateResource is a write +// operation; calling it causes the test to fail immediately. +// -------------------------------------------------------------------------- + +type noWriteActuator struct { + t *testing.T + + // readByIDResult is returned by GetOSResourceByID. + readByIDResult *fakeOSResource + readByIDErr error + + // listResult is returned by ListOSResourcesForImport. + listResult []*fakeOSResource + + // Track which read methods were called so tests can assert that OpenStack + // state was actually fetched. + getByIDCalled bool + listCalled bool +} + +var _ interfaces.CreateResourceActuator[*orcv1alpha1.Flavor, orcv1alpha1.Flavor, orcv1alpha1.FlavorFilter, fakeOSResource] = &noWriteActuator{} + +func (a *noWriteActuator) GetResourceID(r *fakeOSResource) string { + return r.ID +} + +// GetOSResourceByID is a read-only operation: allowed for unmanaged resources. +func (a *noWriteActuator) GetOSResourceByID(_ context.Context, _ string) (*fakeOSResource, progress.ReconcileStatus) { + a.getByIDCalled = true + if a.readByIDErr != nil { + return nil, progress.WrapError(a.readByIDErr) + } + return a.readByIDResult, nil +} + +// ListOSResourcesForAdoption is only called in the creation flow for managed +// resources; for unmanaged resources this path should not be reached when +// statusID or importID is set. +func (a *noWriteActuator) ListOSResourcesForAdoption(_ context.Context, _ *orcv1alpha1.Flavor) (iter.Seq2[*fakeOSResource, error], bool) { + // Return false to signal "no adoption" — this is a safe, read-only path. + return nil, false +} + +// ListOSResourcesForImport is a read-only operation: allowed for unmanaged +// resources using filter-based import. +func (a *noWriteActuator) ListOSResourcesForImport(_ context.Context, _ *orcv1alpha1.Flavor, _ orcv1alpha1.FlavorFilter) (iter.Seq2[*fakeOSResource, error], progress.ReconcileStatus) { + a.listCalled = true + return func(yield func(*fakeOSResource, error) bool) { + for _, r := range a.listResult { + if !yield(r, nil) { + return + } + } + }, nil +} + +// CreateResource is a write operation: MUST NOT be called for unmanaged resources. +func (a *noWriteActuator) CreateResource(_ context.Context, _ *orcv1alpha1.Flavor) (*fakeOSResource, progress.ReconcileStatus) { + a.t.Fatal("CreateResource was called for an unmanaged resource: this is a write operation and MUST NOT be invoked") + return nil, nil +} + +// -------------------------------------------------------------------------- +// fakeAdapter implements interfaces.APIObjectAdapter for *orcv1alpha1.Flavor. +// It delegates all metav1.Object methods to the underlying Flavor (which +// embeds metav1.ObjectMeta and therefore implements metav1.Object). +// -------------------------------------------------------------------------- + +type fakeAdapter struct { + *orcv1alpha1.Flavor +} + +// Ensure fakeAdapter implements APIObjectAdapter at compile time. +var _ interfaces.APIObjectAdapter[*orcv1alpha1.Flavor, orcv1alpha1.FlavorResourceSpec, orcv1alpha1.FlavorFilter] = fakeAdapter{} + +// metav1.Object — all methods delegate to the embedded Flavor (which embeds +// metav1.ObjectMeta). We override only the methods not provided by embedding +// because embedding a non-pointer would copy the object and lose write-backs. + +func (a fakeAdapter) GetUID() types.UID { return a.Flavor.GetUID() } +func (a fakeAdapter) SetUID(uid types.UID) { a.Flavor.SetUID(uid) } +func (a fakeAdapter) GetResourceVersion() string { return a.Flavor.GetResourceVersion() } +func (a fakeAdapter) SetResourceVersion(v string) { a.Flavor.SetResourceVersion(v) } +func (a fakeAdapter) GetGeneration() int64 { return a.Flavor.GetGeneration() } +func (a fakeAdapter) SetGeneration(gen int64) { a.Flavor.SetGeneration(gen) } +func (a fakeAdapter) GetFinalizers() []string { return a.Flavor.GetFinalizers() } +func (a fakeAdapter) SetFinalizers(f []string) { a.Flavor.SetFinalizers(f) } + +// APIObjectAdapter-specific methods. +func (a fakeAdapter) GetObject() *orcv1alpha1.Flavor { return a.Flavor } + +func (a fakeAdapter) GetManagementPolicy() orcv1alpha1.ManagementPolicy { + return a.Spec.ManagementPolicy +} + +func (a fakeAdapter) GetManagedOptions() *orcv1alpha1.ManagedOptions { + return a.Spec.ManagedOptions +} + +func (a fakeAdapter) GetResyncPeriod() *metav1.Duration { + return a.Spec.ResyncPeriod +} + +func (a fakeAdapter) GetLastSyncTime() *metav1.Time { + return a.Status.LastSyncTime +} + +func (a fakeAdapter) GetStatusID() *string { + return a.Status.ID +} + +func (a fakeAdapter) GetResourceSpec() *orcv1alpha1.FlavorResourceSpec { + return a.Spec.Resource +} + +func (a fakeAdapter) GetImportID() *string { + if a.Spec.Import == nil { + return nil + } + return a.Spec.Import.ID +} + +func (a fakeAdapter) GetImportFilter() *orcv1alpha1.FlavorFilter { + if a.Spec.Import == nil { + return nil + } + return a.Spec.Import.Filter +} + +// -------------------------------------------------------------------------- +// fakeResourceController satisfies ResourceController for tests that pre-set +// the finalizer on the ORC object so that no Kubernetes Patch is needed. +// -------------------------------------------------------------------------- + +type fakeResourceController struct{} + +var _ ResourceController = &fakeResourceController{} + +func (c *fakeResourceController) GetName() string { return "test-controller" } + +// GetK8sClient returns nil. If a Kubernetes Patch call is reached during a +// test, the nil dereference will cause a panic — signalling a bug in either +// the test setup (finalizer not pre-set) or the reconciler. +func (c *fakeResourceController) GetK8sClient() client.Client { return nil } + +func (c *fakeResourceController) GetScopeFactory() scope.Factory { return nil } + +// -------------------------------------------------------------------------- +// Helpers for building test Flavors. +// +// All helpers pre-set the controller finalizer so that GetOrCreateOSResource +// does not attempt to call the Kubernetes client to add it. +// -------------------------------------------------------------------------- + +const testControllerName = "test-controller" + +// finalizerFor returns the controller finalizer string for testControllerName. +func finalizerFor() string { + return orcstrings.GetFinalizerName(testControllerName) +} + +// unmanagedFlavorWithStatusID builds an unmanaged Flavor whose status.ID is +// already set (the normal periodic-resync case). +func unmanagedFlavorWithStatusID(statusID string) fakeAdapter { + return fakeAdapter{ + Flavor: &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + Finalizers: []string{finalizerFor()}, + }, + Spec: orcv1alpha1.FlavorSpec{ + ManagementPolicy: orcv1alpha1.ManagementPolicyUnmanaged, + Import: &orcv1alpha1.FlavorImport{ + ID: ptr.To(statusID), + }, + }, + Status: orcv1alpha1.FlavorStatus{ + ID: ptr.To(statusID), + }, + }, + } +} + +// unmanagedFlavorWithImportID builds an unmanaged Flavor that specifies an +// importID but has no statusID yet (before first reconcile). +func unmanagedFlavorWithImportID(importID string) fakeAdapter { + return fakeAdapter{ + Flavor: &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + Finalizers: []string{finalizerFor()}, + }, + Spec: orcv1alpha1.FlavorSpec{ + ManagementPolicy: orcv1alpha1.ManagementPolicyUnmanaged, + Import: &orcv1alpha1.FlavorImport{ + ID: ptr.To(importID), + }, + }, + }, + } +} + +// unmanagedFlavorWithFilter builds an unmanaged Flavor using filter-based +// import with no statusID. +func unmanagedFlavorWithFilter(filter orcv1alpha1.FlavorFilter) fakeAdapter { + return fakeAdapter{ + Flavor: &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + Finalizers: []string{finalizerFor()}, + }, + Spec: orcv1alpha1.FlavorSpec{ + ManagementPolicy: orcv1alpha1.ManagementPolicyUnmanaged, + Import: &orcv1alpha1.FlavorImport{ + Filter: &filter, + }, + }, + }, + } +} + +// unmanagedFlavorNoImport builds an unmanaged Flavor with neither statusID nor +// import — an invalid configuration that API validation normally prevents. +func unmanagedFlavorNoImport() fakeAdapter { + return fakeAdapter{ + Flavor: &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + Finalizers: []string{finalizerFor()}, + }, + Spec: orcv1alpha1.FlavorSpec{ + ManagementPolicy: orcv1alpha1.ManagementPolicyUnmanaged, + }, + }, + } +} + +// managedFlavorWithStatusID builds a managed Flavor whose status.ID is already +// set (the normal steady-state case). +func managedFlavorWithStatusID(statusID string) fakeAdapter { + return fakeAdapter{ + Flavor: &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + Finalizers: []string{finalizerFor()}, + }, + Spec: orcv1alpha1.FlavorSpec{ + ManagementPolicy: orcv1alpha1.ManagementPolicyManaged, + Resource: &orcv1alpha1.FlavorResourceSpec{}, + }, + Status: orcv1alpha1.FlavorStatus{ + ID: ptr.To(statusID), + }, + }, + } +} + +// notFoundErr returns a gophercloud not-found error that orcerrors.IsNotFound +// will recognise. +func notFoundErr() error { + return gophercloud.ErrResourceNotFound{Name: "missing-id", ResourceType: "flavor"} +} + +// -------------------------------------------------------------------------- +// Tests +// -------------------------------------------------------------------------- + +// TestGetOrCreateOSResource_UnmanagedByStatusID verifies that for an unmanaged +// resource whose status.ID is already set (the periodic resync case), only +// GetOSResourceByID is called. No write operation (CreateResource) must be +// invoked (unmanaged resources update status without invoking actuator +// updates). +func TestGetOrCreateOSResource_UnmanagedByStatusID(t *testing.T) { + t.Parallel() + + const resourceID = "test-flavor-id" + osResource := &fakeOSResource{ID: resourceID} + + actuator := &noWriteActuator{t: t, readByIDResult: osResource} + adapter := unmanagedFlavorWithStatusID(resourceID) + + got, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + if needsReschedule, err := rs.NeedsReschedule(); needsReschedule { + t.Fatalf("unexpected reconcile status: needsReschedule=%v err=%v", needsReschedule, err) + } + if got == nil || got.ID != resourceID { + t.Errorf("got resource %v, want ID=%q", got, resourceID) + } + // Verify OpenStack state was fetched (acceptance criterion: unmanaged + // resources still fetch current OpenStack state). + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called: unmanaged resources must still fetch current OpenStack state") + } + if actuator.listCalled { + t.Error("ListOSResourcesForImport was unexpectedly called") + } +} + +// TestGetOrCreateOSResource_UnmanagedByImportID verifies that for an unmanaged +// resource using import-by-ID (no statusID yet), GetOSResourceByID is called +// as a read-only operation and CreateResource is never invoked. +func TestGetOrCreateOSResource_UnmanagedByImportID(t *testing.T) { + t.Parallel() + + const importID = "imported-flavor-id" + osResource := &fakeOSResource{ID: importID} + + actuator := &noWriteActuator{t: t, readByIDResult: osResource} + adapter := unmanagedFlavorWithImportID(importID) + + got, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + if needsReschedule, err := rs.NeedsReschedule(); needsReschedule { + t.Fatalf("unexpected reconcile status: needsReschedule=%v err=%v", needsReschedule, err) + } + if got == nil || got.ID != importID { + t.Errorf("got resource %v, want ID=%q", got, importID) + } + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called: unmanaged resources must still fetch current OpenStack state") + } +} + +// TestGetOrCreateOSResource_UnmanagedByFilter verifies that for an unmanaged +// resource using filter-based import, ListOSResourcesForImport is called as a +// read-only operation and CreateResource is never invoked. +func TestGetOrCreateOSResource_UnmanagedByFilter(t *testing.T) { + t.Parallel() + + osResource := &fakeOSResource{ID: "filter-flavor-id"} + filter := orcv1alpha1.FlavorFilter{ + Name: ptr.To[orcv1alpha1.OpenStackName]("my-flavor"), + } + + actuator := &noWriteActuator{t: t, listResult: []*fakeOSResource{osResource}} + adapter := unmanagedFlavorWithFilter(filter) + + got, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + if needsReschedule, err := rs.NeedsReschedule(); needsReschedule { + t.Fatalf("unexpected reconcile status: needsReschedule=%v err=%v", needsReschedule, err) + } + if got == nil || got.ID != osResource.ID { + t.Errorf("got resource %v, want ID=%q", got, osResource.ID) + } + if !actuator.listCalled { + t.Error("ListOSResourcesForImport was not called: unmanaged resources must still fetch current OpenStack state") + } +} + +// TestGetOrCreateOSResource_UnmanagedNoImport verifies that an unmanaged +// resource with no import configuration returns a terminal error without calling +// CreateResource. API validation should prevent this state in +// production, but the reconciler must handle it safely. +func TestGetOrCreateOSResource_UnmanagedNoImport(t *testing.T) { + t.Parallel() + + actuator := &noWriteActuator{t: t} + adapter := unmanagedFlavorNoImport() + + _, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + _, err := rs.NeedsReschedule() + if err == nil { + t.Fatal("expected a terminal error for unmanaged resource with no import, got nil") + } + + // Verify it is a TerminalError so the controller does not retry uselessly. + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected a TerminalError, got %T: %v", err, err) + } + + if actuator.getByIDCalled { + t.Error("GetOSResourceByID was unexpectedly called") + } + if actuator.listCalled { + t.Error("ListOSResourcesForImport was unexpectedly called") + } +} + +// TestGetOrCreateOSResource_UnmanagedStatusIDDeleted verifies that when an +// unmanaged resource's OpenStack resource has been deleted externally (the +// controller receives a not-found error), the controller returns a terminal +// error rather than calling CreateResource. +func TestGetOrCreateOSResource_UnmanagedStatusIDDeleted(t *testing.T) { + t.Parallel() + + const resourceID = "deleted-flavor-id" + + // Simulate OpenStack returning a 404 / not-found. + actuator := &noWriteActuator{t: t, readByIDErr: notFoundErr()} + adapter := unmanagedFlavorWithStatusID(resourceID) + + _, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + _, err := rs.NeedsReschedule() + if err == nil { + t.Fatal("expected an error when OpenStack resource is not found, got nil") + } + + // The error must be terminal: the resource was deleted externally and cannot + // be recovered by the controller. + var termErr *orcerrors.TerminalError + if !errors.As(err, &termErr) { + t.Errorf("expected a TerminalError for externally-deleted resource, got %T: %v", err, err) + } + + // The controller must still have attempted to fetch the resource. + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called: the controller should attempt to fetch the resource before concluding it is gone") + } +} + +// TestGetOrCreateOSResource_ManagedStatusIDDeleted verifies that when a managed +// resource's OpenStack resource has been deleted externally, the controller +// returns an ExternallyDeleted status to trigger recreation rather than a +// terminal error. +func TestGetOrCreateOSResource_ManagedStatusIDDeleted(t *testing.T) { + t.Parallel() + + const resourceID = "deleted-managed-flavor-id" + + // Simulate OpenStack returning a 404 / not-found. + actuator := &noWriteActuator{t: t, readByIDErr: notFoundErr()} + adapter := managedFlavorWithStatusID(resourceID) + + got, rs := GetOrCreateOSResource(context.Background(), logr.Discard(), &fakeResourceController{}, adapter, actuator) + + // Expect a typed signal: status.id should be cleared and recreation triggered. + if !rs.IsExternallyDeleted() { + t.Fatal("expected IsExternallyDeleted for externally-deleted managed resource") + } + needsReschedule, err := rs.NeedsReschedule() + if needsReschedule { + t.Fatalf("expected no rescheduling (nil reconcileStatus) for externally-deleted managed resource, got needsReschedule=%v err=%v", needsReschedule, err) + } + if got != nil { + t.Errorf("expected nil osResource for recreation path, got %v", got) + } + + // The controller must still have attempted to fetch the resource. + if !actuator.getByIDCalled { + t.Error("GetOSResourceByID was not called") + } +} diff --git a/internal/controllers/generic/resync/config_test.go b/internal/controllers/generic/resync/config_test.go new file mode 100644 index 000000000..afa86eaaf --- /dev/null +++ b/internal/controllers/generic/resync/config_test.go @@ -0,0 +1,125 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 resync + +import ( + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestDetermineResyncPeriod(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + specValue *metav1.Duration + globalDefault time.Duration + want time.Duration + }{ + { + // spec nil, global disabled → disabled + name: "spec nil, global 0, returns 0 (disabled)", + specValue: nil, + globalDefault: 0, + want: 0, + }, + { + // spec nil, global set → use global + name: "spec nil, global 1h, returns 1h", + specValue: nil, + globalDefault: time.Hour, + want: time.Hour, + }, + { + // spec overrides global + name: "spec 30m, global 1h, returns 30m (spec overrides)", + specValue: &metav1.Duration{Duration: 30 * time.Minute}, + globalDefault: time.Hour, + want: 30 * time.Minute, + }, + { + // explicit 0s in spec disables resync regardless of global + name: "spec 0s (explicit), global 1h, returns 0 (explicitly disabled)", + specValue: &metav1.Duration{Duration: 0}, + globalDefault: time.Hour, + want: 0, + }, + { + // spec enables resync even when global is disabled + name: "spec 2h, global 0, returns 2h (spec enables despite global disabled)", + specValue: &metav1.Duration{Duration: 2 * time.Hour}, + globalDefault: 0, + want: 2 * time.Hour, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := DetermineResyncPeriod(tt.specValue, tt.globalDefault) + if got != tt.want { + t.Errorf("DetermineResyncPeriod() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRemainingUntilNextSync(t *testing.T) { + t.Parallel() + + t.Run("disabled", func(t *testing.T) { + t.Parallel() + + lastSync := metav1.NewTime(time.Now().Add(-time.Minute)) + if got := RemainingUntilNextSync(&lastSync, 0); got != 0 { + t.Fatalf("RemainingUntilNextSync() = %v, want 0", got) + } + }) + + t.Run("nil last sync", func(t *testing.T) { + t.Parallel() + + if got := RemainingUntilNextSync(nil, 10*time.Minute); got != 0 { + t.Fatalf("RemainingUntilNextSync() = %v, want 0", got) + } + }) + + t.Run("period elapsed", func(t *testing.T) { + t.Parallel() + + lastSync := metav1.NewTime(time.Now().Add(-20 * time.Minute)) + if got := RemainingUntilNextSync(&lastSync, 10*time.Minute); got != 0 { + t.Fatalf("RemainingUntilNextSync() = %v, want 0", got) + } + }) + + t.Run("period not elapsed", func(t *testing.T) { + t.Parallel() + + lastSync := metav1.NewTime(time.Now().Add(-2 * time.Minute)) + got := RemainingUntilNextSync(&lastSync, 10*time.Minute) + if got <= 0 { + t.Fatalf("RemainingUntilNextSync() = %v, want positive duration", got) + } + if got > 8*time.Minute || got < 7*time.Minute { + t.Fatalf("RemainingUntilNextSync() = %v, want approximately 8m", got) + } + }) +} diff --git a/internal/controllers/generic/resync/scheduler_test.go b/internal/controllers/generic/resync/scheduler_test.go new file mode 100644 index 000000000..10cf8c92e --- /dev/null +++ b/internal/controllers/generic/resync/scheduler_test.go @@ -0,0 +1,216 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 resync + +import ( + "fmt" + "testing" + "time" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// TestCalculateJitteredDuration_Range verifies that the returned duration is +// always within [base*1.0, base*1.2) across many calls (acceptance criterion). +func TestCalculateJitteredDuration_Range(t *testing.T) { + t.Parallel() + + const ( + base = 10 * time.Minute + samples = 1000 + ) + + lo := time.Duration(float64(base) * 1.0) + hi := time.Duration(float64(base) * 1.2) + + for i := range samples { + d := CalculateJitteredDuration(base) + if d < lo || d > hi { + t.Errorf("sample %d: CalculateJitteredDuration(%v) = %v, want in [%v, %v]", i, base, d, lo, hi) + } + } +} + +// TestCalculateJitteredDuration_Uniformity verifies that the jitter +// distribution is statistically uniform by checking that all 10 buckets across +// [base*1.0, base*1.2) are populated with at least 1/20th of the expected +// frequency (very conservative check to avoid flakiness while still catching +// obvious bias). +func TestCalculateJitteredDuration_Uniformity(t *testing.T) { + t.Parallel() + + const ( + base = time.Hour + samples = 10000 + buckets = 10 + ) + + lo := float64(base) * 1.0 + hi := float64(base) * (1 + jitterFactor) + width := (hi - lo) / buckets + + counts := make([]int, buckets) + for range samples { + d := CalculateJitteredDuration(base) + idx := int((float64(d) - lo) / width) + // Clamp to handle floating-point edge at the top of the range. + if idx >= buckets { + idx = buckets - 1 + } + if idx < 0 { + idx = 0 + } + counts[idx]++ + } + + // Each bucket should receive roughly samples/buckets hits. Require at + // least 1/3 of the expected count to avoid flakiness while catching bias. + minExpected := (samples / buckets) / 3 + for i, c := range counts { + if c < minExpected { + t.Errorf("bucket %d: count %d is below minimum expected %d (distribution is not uniform)", i, c, minExpected) + } + } +} + +// TestCalculateJitteredDuration_Independence verifies that multiple resources +// receive independent jitter values: calling the function twice with +// the same base should produce different values in the vast majority of cases. +func TestCalculateJitteredDuration_Independence(t *testing.T) { + t.Parallel() + + const ( + base = time.Hour + samples = 100 + ) + + unique := make(map[time.Duration]struct{}, samples) + for range samples { + d := CalculateJitteredDuration(base) + unique[d] = struct{}{} + } + + // Expect nearly all samples to be distinct. Allow for at most 5% + // collisions (extremely conservative; in practice collisions are + // essentially impossible with nanosecond precision). + minUnique := samples * 95 / 100 + if len(unique) < minUnique { + t.Errorf("CalculateJitteredDuration produced only %d unique values out of %d samples (expected >= %d); values may not be independent", len(unique), samples, minUnique) + } +} + +// TestCalculateJitteredDuration_ZeroBase verifies behaviour with a zero base. +func TestCalculateJitteredDuration_ZeroBase(t *testing.T) { + t.Parallel() + + if d := CalculateJitteredDuration(0); d != 0 { + t.Errorf("CalculateJitteredDuration(0) = %v, want 0", d) + } +} + +// TestShouldScheduleResync covers all documented return-false conditions and +// the happy path. +func TestShouldScheduleResync(t *testing.T) { + t.Parallel() + + terminalErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "bad config") + transientErr := fmt.Errorf("transient error") + + tests := []struct { + name string + resyncPeriod time.Duration + reconcileStatus progress.ReconcileStatus + want bool + }{ + { + // resync disabled globally + name: "resyncPeriod 0, nil status, returns false", + resyncPeriod: 0, + reconcileStatus: nil, + want: false, + }, + { + // resyncPeriod negative: treated as disabled + name: "resyncPeriod negative, nil status, returns false", + resyncPeriod: -time.Second, + reconcileStatus: nil, + want: false, + }, + { + // terminal error → no resync + name: "terminal error in status, returns false", + resyncPeriod: time.Hour, + reconcileStatus: progress.WrapError(terminalErr), + want: false, + }, + { + // requeue already pending → resync is redundant + name: "requeue already pending in status, returns false", + resyncPeriod: time.Hour, + reconcileStatus: progress.NewReconcileStatus().WithRequeue(5 * time.Second), + want: false, + }, + { + // Happy path: positive period, no terminal error, no pending requeue + name: "positive period, nil status, returns true", + resyncPeriod: time.Hour, + reconcileStatus: nil, + want: true, + }, + { + // Happy path: transient (non-terminal) error should not suppress resync + name: "transient error in status, returns true", + resyncPeriod: time.Hour, + reconcileStatus: progress.WrapError(transientErr), + want: true, + }, + { + // Happy path: progress message with no requeue should not suppress resync + name: "progress message only, no requeue, returns true", + resyncPeriod: time.Hour, + reconcileStatus: progress.NewReconcileStatus().WithProgressMessage("waiting for dependency"), + want: true, + }, + { + // Terminal error takes precedence even when period is positive + name: "terminal error with progress message, returns false", + resyncPeriod: time.Hour, + reconcileStatus: progress.WrapError(terminalErr).WithProgressMessage("some message"), + want: false, + }, + { + // Requeue takes precedence when period is positive + name: "requeue pending with progress message, returns false", + resyncPeriod: 30 * time.Minute, + reconcileStatus: progress.NewReconcileStatus().WithRequeue(10 * time.Second).WithProgressMessage("waiting"), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := ShouldScheduleResync(tt.resyncPeriod, tt.reconcileStatus) + if got != tt.want { + t.Errorf("ShouldScheduleResync(%v, ...) = %v, want %v", tt.resyncPeriod, got, tt.want) + } + }) + } +} diff --git a/internal/controllers/generic/status/conditions_test.go b/internal/controllers/generic/status/conditions_test.go new file mode 100644 index 000000000..ba821d02e --- /dev/null +++ b/internal/controllers/generic/status/conditions_test.go @@ -0,0 +1,155 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 status + +import ( + "testing" + + 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/progress" + 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" +) + +// TestSetCommonConditions_TerminalErrorOverridesUnknownToFalse verifies that +// when ResourceAvailableStatus returns ConditionUnknown and a terminal error is +// present, SetCommonConditions overrides the Available condition to False. +// +// This is the fix for the network-external-deletion-import CI failure: when an +// imported network is externally deleted, ORC sets a terminal error but +// ResourceAvailableStatus returns ConditionUnknown (because Status.ID is set +// but the OS resource is nil). The Available condition must be False, not +// Unknown, when a terminal error is present. +func TestSetCommonConditions_TerminalErrorOverridesUnknownToFalse(t *testing.T) { + t.Parallel() + + flavor := &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + }, + } + + termErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "resource has been deleted from OpenStack", nil) + reconcileStatus := progress.WrapError(termErr) + + applyConfigStatus := orcapplyconfigv1alpha1.FlavorStatus() + now := metav1.Now() + + // Call SetCommonConditions with ConditionUnknown (as ResourceAvailableStatus + // returns when osResource==nil and Status.ID!=nil) and a terminal error. + SetCommonConditions(flavor, applyConfigStatus, metav1.ConditionUnknown, reconcileStatus, now) + + // Find the Available condition in the resulting apply configuration. + var availableCondition *metav1.ConditionStatus + for i := range applyConfigStatus.Conditions { + if applyConfigStatus.Conditions[i].Type != nil && *applyConfigStatus.Conditions[i].Type == orcv1alpha1.ConditionAvailable { + availableCondition = applyConfigStatus.Conditions[i].Status + break + } + } + + if availableCondition == nil { + t.Fatal("Available condition not set in apply configuration") + } + + if *availableCondition != metav1.ConditionFalse { + t.Errorf("Available condition status = %q; want %q (terminal error should override Unknown → False)", + *availableCondition, metav1.ConditionFalse) + } +} + +// TestSetCommonConditions_TerminalErrorWithFalseRemainingFalse verifies that +// when ResourceAvailableStatus already returns ConditionFalse and a terminal +// error is present, the Available condition remains False (not changed). +func TestSetCommonConditions_TerminalErrorWithFalseRemainingFalse(t *testing.T) { + t.Parallel() + + flavor := &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + }, + } + + termErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "resource has been deleted from OpenStack", nil) + reconcileStatus := progress.WrapError(termErr) + + applyConfigStatus := orcapplyconfigv1alpha1.FlavorStatus() + now := metav1.Now() + + // Call SetCommonConditions with ConditionFalse (resource not found, no ID). + SetCommonConditions(flavor, applyConfigStatus, metav1.ConditionFalse, reconcileStatus, now) + + var availableCondition *metav1.ConditionStatus + for i := range applyConfigStatus.Conditions { + if applyConfigStatus.Conditions[i].Type != nil && *applyConfigStatus.Conditions[i].Type == orcv1alpha1.ConditionAvailable { + availableCondition = applyConfigStatus.Conditions[i].Status + break + } + } + + if availableCondition == nil { + t.Fatal("Available condition not set in apply configuration") + } + + if *availableCondition != metav1.ConditionFalse { + t.Errorf("Available condition status = %q; want %q", *availableCondition, metav1.ConditionFalse) + } +} + +// TestSetCommonConditions_NoTerminalErrorKeepsUnknown verifies that when +// ResourceAvailableStatus returns ConditionUnknown and no terminal error is +// present (e.g. a transient error or progress), the Available condition remains +// Unknown. +func TestSetCommonConditions_NoTerminalErrorKeepsUnknown(t *testing.T) { + t.Parallel() + + flavor := &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + }, + } + + // Transient error (not terminal) — Available should remain Unknown. + reconcileStatus := progress.NewReconcileStatus().WithProgressMessage("waiting for OpenStack") + + applyConfigStatus := orcapplyconfigv1alpha1.FlavorStatus() + now := metav1.Now() + + SetCommonConditions(flavor, applyConfigStatus, metav1.ConditionUnknown, reconcileStatus, now) + + var availableCondition *metav1.ConditionStatus + for i := range applyConfigStatus.Conditions { + if applyConfigStatus.Conditions[i].Type != nil && *applyConfigStatus.Conditions[i].Type == orcv1alpha1.ConditionAvailable { + availableCondition = applyConfigStatus.Conditions[i].Status + break + } + } + + if availableCondition == nil { + t.Fatal("Available condition not set in apply configuration") + } + + if *availableCondition != metav1.ConditionUnknown { + t.Errorf("Available condition status = %q; want %q (no terminal error should not change Unknown)", + *availableCondition, metav1.ConditionUnknown) + } +} diff --git a/internal/controllers/generic/status/status_test.go b/internal/controllers/generic/status/status_test.go new file mode 100644 index 000000000..18d031624 --- /dev/null +++ b/internal/controllers/generic/status/status_test.go @@ -0,0 +1,228 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 status + +import ( + "context" + "errors" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + 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/scope" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// TestShouldSetLastSyncTime_SuccessfulReconciliation verifies that lastSyncTime +// is set when reconcileStatus is nil (clean success, no errors, no progress +// messages). This is the common case after a successful OpenStack API read. +func TestShouldSetLastSyncTime_SuccessfulReconciliation(t *testing.T) { + t.Parallel() + + // nil ReconcileStatus represents a clean, successful reconciliation. + var rs progress.ReconcileStatus + if !shouldSetLastSyncTime(rs) { + t.Error("shouldSetLastSyncTime(nil) = false; want true for successful reconciliation") + } +} + +// TestShouldSetLastSyncTime_WithRequeueOnly verifies that a requeue alone +// (e.g., for a periodic resync) does not prevent lastSyncTime from being set. +// A pending requeue without errors or progress messages still counts as a +// successful reconciliation cycle. +func TestShouldSetLastSyncTime_WithRequeueOnly(t *testing.T) { + t.Parallel() + + // A requeue alone does not contribute to NeedsReschedule. + rs := progress.NewReconcileStatus().WithRequeue(10 * time.Minute) + if !shouldSetLastSyncTime(rs) { + t.Error("shouldSetLastSyncTime(requeue-only) = false; want true: requeue alone should not prevent lastSyncTime update") + } +} + +// TestShouldSetLastSyncTime_WithError verifies that lastSyncTime is NOT set +// when reconcileStatus contains an error. An error means the controller did not +// successfully complete the reconciliation cycle. +func TestShouldSetLastSyncTime_WithError(t *testing.T) { + t.Parallel() + + rs := progress.WrapError(errors.New("transient openstack error")) + if shouldSetLastSyncTime(rs) { + t.Error("shouldSetLastSyncTime(error) = true; want false: errors should prevent lastSyncTime update") + } +} + +// TestShouldSetLastSyncTime_WithTerminalError verifies that lastSyncTime is NOT +// set when reconcileStatus contains a terminal error. Terminal errors indicate +// a non-retryable failure; the reconciliation did not succeed. +func TestShouldSetLastSyncTime_WithTerminalError(t *testing.T) { + t.Parallel() + + termErr := orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration", nil) + rs := progress.WrapError(termErr) + if shouldSetLastSyncTime(rs) { + t.Error("shouldSetLastSyncTime(terminal error) = true; want false: terminal errors should prevent lastSyncTime update") + } +} + +// TestShouldSetLastSyncTime_WithProgressMessage verifies that lastSyncTime is +// NOT set when reconcileStatus contains a progress message. Progress messages +// indicate that the reconciliation is still ongoing (waiting on a dependency, +// resource not yet ready, etc.) and has not completed successfully. +func TestShouldSetLastSyncTime_WithProgressMessage(t *testing.T) { + t.Parallel() + + rs := progress.NewReconcileStatus().WithProgressMessage("waiting for resource to become active") + if shouldSetLastSyncTime(rs) { + t.Error("shouldSetLastSyncTime(progress message) = true; want false: progress messages should prevent lastSyncTime update") + } +} + +// TestShouldSetLastSyncTime_WithErrorAndProgressMessage verifies that +// lastSyncTime is NOT set when reconcileStatus contains both an error and a +// progress message. Either alone should be sufficient to suppress the update. +func TestShouldSetLastSyncTime_WithErrorAndProgressMessage(t *testing.T) { + t.Parallel() + + rs := progress.WrapError(errors.New("API error")).WithProgressMessage("still waiting") + if shouldSetLastSyncTime(rs) { + t.Error("shouldSetLastSyncTime(error+progress) = true; want false: any non-success condition should prevent lastSyncTime update") + } +} + +// -------------------------------------------------------------------------- +// fakeStatusController implements interfaces.ResourceController for +// ClearStatusID tests. It wraps a real fake.Client to allow status patch calls. +// -------------------------------------------------------------------------- + +type fakeStatusController struct { + k8sClient client.Client +} + +var _ interfaces.ResourceController = &fakeStatusController{} + +func (c *fakeStatusController) GetName() string { return "test-status-controller" } +func (c *fakeStatusController) GetK8sClient() client.Client { return c.k8sClient } +func (c *fakeStatusController) GetScopeFactory() scope.Factory { return nil } + +// TestClearStatusID_SendsMergePatchWithNullID verifies that ClearStatusID +// issues a JSON merge patch that sets status.id to null. The function is +// expected to be called by reconcileNormal when an externally deleted managed +// resource is detected (GetOrCreateOSResource returns nil, nil). +func TestClearStatusID_SendsMergePatchWithNullID(t *testing.T) { + t.Parallel() + + const resourceID = "some-os-id" + + // Build a Flavor with status.ID already set (simulating a managed resource + // whose OpenStack counterpart was deleted externally). + flavor := &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + UID: types.UID("test-uid"), + }, + Status: orcv1alpha1.FlavorStatus{ + ID: ptr.To(resourceID), + }, + } + + // Register the Flavor scheme so the fake client can handle it. + scheme := runtime.NewScheme() + if err := orcv1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add orcv1alpha1 to scheme: %v", err) + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithStatusSubresource(&orcv1alpha1.Flavor{}). + WithObjects(flavor). + Build() + + controller := &fakeStatusController{k8sClient: fakeClient} + + // Call ClearStatusID: should patch status.id to null. + if err := ClearStatusID(context.Background(), controller, flavor); err != nil { + t.Fatalf("ClearStatusID returned unexpected error: %v", err) + } + + // Fetch the updated Flavor and verify status.ID is now nil. + updated := &orcv1alpha1.Flavor{} + if err := fakeClient.Get(context.Background(), client.ObjectKey{Name: "test-flavor", Namespace: "default"}, updated); err != nil { + t.Fatalf("failed to get updated flavor: %v", err) + } + + if updated.Status.ID != nil { + t.Errorf("status.id = %q after ClearStatusID; want nil (cleared)", *updated.Status.ID) + } +} + +// TestClearStatusID_GroupVersionResource verifies that ClearStatusID targets +// the status subresource (i.e., calls Status().Patch rather than Patch). +// This is an indirect check: if ClearStatusID called the main Patch instead of +// Status().Patch, the fake client with WithStatusSubresource would not update +// the status and the ID would remain set. +func TestClearStatusID_IdempotentWhenAlreadyNil(t *testing.T) { + t.Parallel() + + // Flavor with no status.ID (already cleared or never set). + flavor := &orcv1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-flavor", + Namespace: "default", + UID: types.UID("test-uid"), + }, + // Status.ID is nil by default. + } + + scheme := runtime.NewScheme() + if err := orcv1alpha1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add orcv1alpha1 to scheme: %v", err) + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithStatusSubresource(&orcv1alpha1.Flavor{}). + WithObjects(flavor). + Build() + + controller := &fakeStatusController{k8sClient: fakeClient} + + // ClearStatusID should succeed even when status.id is already nil. + if err := ClearStatusID(context.Background(), controller, flavor); err != nil { + t.Fatalf("ClearStatusID returned unexpected error on already-nil ID: %v", err) + } + + // Status.ID should remain nil. + updated := &orcv1alpha1.Flavor{} + if err := fakeClient.Get(context.Background(), client.ObjectKey{Name: "test-flavor", Namespace: "default"}, updated); err != nil { + t.Fatalf("failed to get flavor after ClearStatusID: %v", err) + } + + if updated.Status.ID != nil { + t.Errorf("status.id = %q after ClearStatusID on already-nil ID; want nil", *updated.Status.ID) + } +} diff --git a/internal/controllers/network/tests/network-external-deletion-import/00-assert.yaml b/internal/controllers/network/tests/network-external-deletion-import/00-assert.yaml new file mode 100644 index 000000000..1a9230468 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/00-assert.yaml @@ -0,0 +1,14 @@ +--- +# Verify the external network is available before proceeding with the import. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion-import-external +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/network/tests/network-external-deletion-import/00-create-external.yaml b/internal/controllers/network/tests/network-external-deletion-import/00-create-external.yaml new file mode 100644 index 000000000..124ecc841 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/00-create-external.yaml @@ -0,0 +1,15 @@ +--- +# Create a managed network in OpenStack via ORC. This network will later be +# imported as an unmanaged resource, then deleted externally to verify that +# ORC produces a terminal error (rather than recreating it). +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion-import-external +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Network from "external-deletion-import" test diff --git a/internal/controllers/network/tests/network-external-deletion-import/00-secret.yaml b/internal/controllers/network/tests/network-external-deletion-import/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/00-secret.yaml @@ -0,0 +1,5 @@ +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/network/tests/network-external-deletion-import/01-assert.yaml b/internal/controllers/network/tests/network-external-deletion-import/01-assert.yaml new file mode 100644 index 000000000..4429a2653 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/01-assert.yaml @@ -0,0 +1,22 @@ +--- +# Verify the imported network is available, confirming the import succeeded. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion-import +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success + resource: + name: network-external-deletion-import-external + description: Network from "external-deletion-import" test + adminStateUp: true + external: false + portSecurityEnabled: true + shared: false + status: ACTIVE diff --git a/internal/controllers/network/tests/network-external-deletion-import/01-import-resource.yaml b/internal/controllers/network/tests/network-external-deletion-import/01-import-resource.yaml new file mode 100644 index 000000000..a87f147bf --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/01-import-resource.yaml @@ -0,0 +1,19 @@ +--- +# Import the external network into ORC as an unmanaged resource. The import +# filter uses the unique description to identify the network created in step 00. +# A short resyncPeriod ensures ORC checks the network state periodically, so +# external deletion is detected without requiring a manual trigger. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + # resyncPeriod of 10s ensures ORC detects the external deletion quickly. + resyncPeriod: 10s + import: + filter: + description: Network from "external-deletion-import" test diff --git a/internal/controllers/network/tests/network-external-deletion-import/02-assert.yaml b/internal/controllers/network/tests/network-external-deletion-import/02-assert.yaml new file mode 100644 index 000000000..1a69ee107 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/02-assert.yaml @@ -0,0 +1,28 @@ +--- +# After the OpenStack network is deleted externally, ORC detects on the next +# reconcile (within the configured resyncPeriod of 10s) that the resource +# referenced by status.id no longer exists. +# +# Because the resource is unmanaged, ORC cannot recreate it. Instead it returns +# a terminal error, which sets both Progressing and Available to False with +# reason UnrecoverableError. +# +# The terminal error prevents any further reconciliation until the spec changes. +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 300 +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion-import +status: + conditions: + - type: Available + message: resource has been deleted from OpenStack + status: "False" + reason: UnrecoverableError + - type: Progressing + message: resource has been deleted from OpenStack + status: "False" + reason: UnrecoverableError diff --git a/internal/controllers/network/tests/network-external-deletion-import/02-delete-from-openstack.yaml b/internal/controllers/network/tests/network-external-deletion-import/02-delete-from-openstack.yaml new file mode 100644 index 000000000..085a44867 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/02-delete-from-openstack.yaml @@ -0,0 +1,23 @@ +--- +# Delete the OpenStack network directly (bypassing ORC). We get the OpenStack +# ID from the unmanaged import's status.id and use the OpenStack CLI to remove +# the network without going through ORC. +# +# After this deletion, the unmanaged ORC object (network-external-deletion-import) +# still has status.id pointing to the now-deleted network. On the next reconcile +# (triggered by the resyncPeriod), ORC calls GetOSResourceByID and gets NotFound. +# Since the resource is unmanaged, ORC cannot recreate it and instead sets a +# terminal error (UnrecoverableError). +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + # Get the OpenStack ID referenced by the imported (unmanaged) ORC object. + NETWORK_ID=$(kubectl get network.openstack.k-orc.cloud network-external-deletion-import \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.id}') + + # Delete the network directly in OpenStack, bypassing ORC. + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack + openstack network delete "${NETWORK_ID}" diff --git a/internal/controllers/network/tests/network-external-deletion-import/README.md b/internal/controllers/network/tests/network-external-deletion-import/README.md new file mode 100644 index 000000000..d0969093f --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion-import/README.md @@ -0,0 +1,27 @@ +# External deletion of an imported (unmanaged) Network produces a terminal error + +## Step 00 + +Create an external managed Network that will be used as the import target, +and wait for it to become available in OpenStack. + +## Step 01 + +Import the external network into ORC as an unmanaged resource (using an import +filter). Verify the import succeeds and the network is available. + +## Step 02 + +Delete the external OpenStack network directly (bypassing ORC). On the next +reconcile, ORC detects that the network referenced by `status.id` no longer +exists in OpenStack. Because the resource was originally imported (unmanaged), +ORC cannot recreate it - instead it sets a terminal error condition +(`UnrecoverableError`) with the message "resource has been deleted from +OpenStack". No further reconciliation occurs. + +## Reference + +Tests the external deletion handling for imported/unmanaged resources as +described in `resource_actions.go`: when a resource was originally imported +and is found to be missing from OpenStack, ORC returns a terminal error instead +of attempting recreation. diff --git a/internal/controllers/network/tests/network-external-deletion/00-assert.yaml b/internal/controllers/network/tests/network-external-deletion/00-assert.yaml new file mode 100644 index 000000000..01ad49f68 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion/00-assert.yaml @@ -0,0 +1,41 @@ +--- +# Verify the network is available and has a status.id set (OpenStack network ID). +# Record the original ID so we can compare after recreation in step 01. +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-external-deletion + ref: network +assertAll: + # Verify the OpenStack ID is set before we delete the network externally. + - celExpr: "network.status.id != ''" +commands: + - script: | + # Save the original OpenStack network ID for comparison in step 01. + kubectl get network.openstack.k-orc.cloud network-external-deletion \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.id}' \ + > /tmp/network-external-deletion-original-id +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success + resource: + name: network-external-deletion + description: Network from "external-deletion" test + adminStateUp: true + external: false + portSecurityEnabled: true + shared: false + status: ACTIVE diff --git a/internal/controllers/network/tests/network-external-deletion/00-create-resource.yaml b/internal/controllers/network/tests/network-external-deletion/00-create-resource.yaml new file mode 100644 index 000000000..95d4833ba --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion/00-create-resource.yaml @@ -0,0 +1,18 @@ +--- +# Create a managed Network resource and wait for ORC to create it in OpenStack. +# A short resyncPeriod ensures ORC checks the network state periodically, so +# external deletion is detected without requiring a manual trigger. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # resyncPeriod of 10s ensures ORC detects the external deletion quickly + # without requiring a watch event or manual trigger. + resyncPeriod: 10s + resource: + description: Network from "external-deletion" test diff --git a/internal/controllers/network/tests/network-external-deletion/00-secret.yaml b/internal/controllers/network/tests/network-external-deletion/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion/00-secret.yaml @@ -0,0 +1,5 @@ +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/network/tests/network-external-deletion/01-assert.yaml b/internal/controllers/network/tests/network-external-deletion/01-assert.yaml new file mode 100644 index 000000000..9d3712ea7 --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion/01-assert.yaml @@ -0,0 +1,50 @@ +--- +# After the OpenStack network is deleted externally, ORC detects the deletion +# via the configured resyncPeriod (10s). ORC clears status.id and recreates the +# network in OpenStack. Verify that: +# 1. The network is available again with correct conditions and resource status. +# 2. The OpenStack ID (status.id) has changed - a brand-new network was created. +# +# The timeout is set generously to allow for the resyncPeriod to elapse, the +# deletion to be detected, and the new network to be created and reach ACTIVE. +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 300 +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-external-deletion + ref: network +assertAll: + # The new OpenStack ID must be set and non-empty. + - celExpr: "network.status.id != ''" +commands: + - script: | + ORIGINAL=$(cat /tmp/network-external-deletion-original-id) + CURRENT=$(kubectl get network.openstack.k-orc.cloud network-external-deletion \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.id}') + # Succeed only when both IDs are set and the new ID differs from the original, + # confirming that ORC detected the external deletion and recreated the network. + [ -n "${ORIGINAL}" ] && [ -n "${CURRENT}" ] && [ "${CURRENT}" != "${ORIGINAL}" ] +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-external-deletion +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success + resource: + name: network-external-deletion + description: Network from "external-deletion" test + adminStateUp: true + external: false + portSecurityEnabled: true + shared: false + status: ACTIVE diff --git a/internal/controllers/network/tests/network-external-deletion/01-delete-from-openstack.yaml b/internal/controllers/network/tests/network-external-deletion/01-delete-from-openstack.yaml new file mode 100644 index 000000000..b932ef0ce --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion/01-delete-from-openstack.yaml @@ -0,0 +1,16 @@ +--- +# Delete the network directly in OpenStack, bypassing ORC. +# This simulates an external deletion event (e.g., an operator accidentally +# deleting the resource, or a garbage-collection script removing it). +# The resyncPeriod configured on the ORC object ensures ORC will detect +# the deletion within the configured period (10s) without needing a manual trigger. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + NETWORK_ID=$(cat /tmp/network-external-deletion-original-id) + + # Delete the network directly in OpenStack, bypassing ORC. + cd $(dirname ${E2E_KUTTL_OSCLOUDS}) + export OS_CLOUD=openstack + openstack network delete "${NETWORK_ID}" diff --git a/internal/controllers/network/tests/network-external-deletion/README.md b/internal/controllers/network/tests/network-external-deletion/README.md new file mode 100644 index 000000000..0ebe9310b --- /dev/null +++ b/internal/controllers/network/tests/network-external-deletion/README.md @@ -0,0 +1,29 @@ +# External deletion and recreation of a managed Network + +## Step 00 + +Create a managed Network resource with a short `resyncPeriod` (10s) and wait +for ORC to create it in OpenStack and report it as available. Record the +OpenStack network ID assigned by ORC. + +The `resyncPeriod` ensures ORC checks the network state periodically, allowing +it to detect external deletion without requiring a manual trigger or watch event. + +## Step 01 + +Delete the OpenStack network directly (bypassing ORC). ORC detects the deletion +on the next periodic resync (within 10s), clears `status.id`, and recreates +the network in OpenStack on the following reconcile. + +Verify that: +- The network is available again with correct conditions and resource status. +- The OpenStack ID in `status.id` has changed (a new network was created, + confirming ORC detected the external deletion and recreated the resource). + +## Reference + +Tests the external deletion handling for managed resources as described in +`internal/controllers/generic/reconciler/resource_actions.go`: when a managed, +non-imported resource is found to be missing from OpenStack (the ID in +`status.id` no longer exists), ORC clears `status.id` and recreates the +resource on the next reconcile. diff --git a/internal/controllers/network/tests/network-resync-disabled/00-assert.yaml b/internal/controllers/network/tests/network-resync-disabled/00-assert.yaml new file mode 100644 index 000000000..9b0edfd06 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-disabled/00-assert.yaml @@ -0,0 +1,33 @@ +--- +# Verify the network is available and lastSyncTime has been set after the +# initial reconciliation, even with resyncPeriod=0 (disabled periodic resync). +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-resync-disabled + ref: network +assertAll: + - celExpr: "has(network.status.lastSyncTime)" + - celExpr: "network.status.lastSyncTime != ''" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-disabled +status: + resource: + name: network-resync-disabled + adminStateUp: true + external: false + portSecurityEnabled: true + shared: false + status: ACTIVE + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/network/tests/network-resync-disabled/00-create-resource.yaml b/internal/controllers/network/tests/network-resync-disabled/00-create-resource.yaml new file mode 100644 index 000000000..3e1cf5fff --- /dev/null +++ b/internal/controllers/network/tests/network-resync-disabled/00-create-resource.yaml @@ -0,0 +1,13 @@ +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-disabled +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # resyncPeriod of 0s explicitly disables periodic resync. The controller + # should reconcile the resource once on creation and then not reschedule. + resyncPeriod: 0s + resource: {} diff --git a/internal/controllers/network/tests/network-resync-disabled/00-secret.yaml b/internal/controllers/network/tests/network-resync-disabled/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-disabled/00-secret.yaml @@ -0,0 +1,5 @@ +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/network/tests/network-resync-disabled/01-assert.yaml b/internal/controllers/network/tests/network-resync-disabled/01-assert.yaml new file mode 100644 index 000000000..a645a3f05 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-disabled/01-assert.yaml @@ -0,0 +1,15 @@ +--- +# Verify the network is still available and stable after the waiting period. +# No changes should have occurred since no resync was triggered. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-disabled +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/network/tests/network-resync-disabled/01-check-no-resync.yaml b/internal/controllers/network/tests/network-resync-disabled/01-check-no-resync.yaml new file mode 100644 index 000000000..a74663299 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-disabled/01-check-no-resync.yaml @@ -0,0 +1,17 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +# Record the current lastSyncTime, sleep for 15 seconds (longer than even a +# fast resync period would trigger), then verify that lastSyncTime has NOT +# changed. This confirms that resyncPeriod=0 prevents periodic re-reconciliation. +commands: + - script: | + INITIAL=$(kubectl get network.openstack.k-orc.cloud network-resync-disabled \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.lastSyncTime}') + # Sleep longer than any reasonable minimum resync period to confirm no resync fires. + sleep 15 + CURRENT=$(kubectl get network.openstack.k-orc.cloud network-resync-disabled \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.lastSyncTime}') + # Fail if lastSyncTime changed (would indicate an unexpected resync). + [ "${INITIAL}" = "${CURRENT}" ] diff --git a/internal/controllers/network/tests/network-resync-disabled/README.md b/internal/controllers/network/tests/network-resync-disabled/README.md new file mode 100644 index 000000000..d97bfc2ff --- /dev/null +++ b/internal/controllers/network/tests/network-resync-disabled/README.md @@ -0,0 +1,21 @@ +# Network with resyncPeriod=0 disables periodic resync + +## Step 00 + +Create a network with `resyncPeriod: 0s` (disabled periodic resync) and verify that: +- The network becomes available with correct conditions. +- `lastSyncTime` is set in the status after the first successful reconciliation. + (Even with resync disabled, the controller always records the initial sync time.) + +## Step 01 + +Wait for a period longer than the minimum resync period and verify that +`lastSyncTime` has NOT changed. When `resyncPeriod` is 0 (disabled), the +controller does not schedule additional reconciliations, so `lastSyncTime` +should remain stable after the initial reconciliation. + +## Reference + +Tests that setting `resyncPeriod: 0s` (or omitting resyncPeriod) disables +periodic resync scheduling. The resource is still reconciled on events (spec +changes, dependency updates) but not on a timer. diff --git a/internal/controllers/network/tests/network-resync-jitter/00-assert.yaml b/internal/controllers/network/tests/network-resync-jitter/00-assert.yaml new file mode 100644 index 000000000..f19a692d1 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-jitter/00-assert.yaml @@ -0,0 +1,61 @@ +--- +# Verify all three networks are available and each has lastSyncTime set after +# the initial successful reconciliation. +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-resync-jitter-1 + ref: network1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-resync-jitter-2 + ref: network2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-resync-jitter-3 + ref: network3 +assertAll: + - celExpr: "has(network1.status.lastSyncTime) && network1.status.lastSyncTime != ''" + - celExpr: "has(network2.status.lastSyncTime) && network2.status.lastSyncTime != ''" + - celExpr: "has(network3.status.lastSyncTime) && network3.status.lastSyncTime != ''" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-jitter-1 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-jitter-2 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-jitter-3 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/network/tests/network-resync-jitter/00-create-resources.yaml b/internal/controllers/network/tests/network-resync-jitter/00-create-resources.yaml new file mode 100644 index 000000000..87bc8a883 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-jitter/00-create-resources.yaml @@ -0,0 +1,39 @@ +--- +# Create three networks all sharing the same resyncPeriod to exercise jitter +# scheduling. Each network will be independently scheduled with [0%, +20%] jitter, +# preventing them from all reconciling simultaneously. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-jitter-1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resyncPeriod: 10s + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-jitter-2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resyncPeriod: 10s + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-jitter-3 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resyncPeriod: 10s + resource: {} diff --git a/internal/controllers/network/tests/network-resync-jitter/00-secret.yaml b/internal/controllers/network/tests/network-resync-jitter/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-jitter/00-secret.yaml @@ -0,0 +1,5 @@ +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/network/tests/network-resync-jitter/01-assert.yaml b/internal/controllers/network/tests/network-resync-jitter/01-assert.yaml new file mode 100644 index 000000000..86c345e3b --- /dev/null +++ b/internal/controllers/network/tests/network-resync-jitter/01-assert.yaml @@ -0,0 +1,39 @@ +--- +# After the resync period elapses, all three networks should have been +# independently re-reconciled. This assert waits for all three lastSyncTime +# values to advance beyond their recorded initial values and verifies that not +# all elapsed intervals are identical, confirming that jitter is observable in +# the end-to-end flow without requiring every random sample to be unique. +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 240 +commands: + - script: | + set -eu + + INIT1=$(cat /tmp/network-resync-jitter-1-initial-sync-time) + INIT2=$(cat /tmp/network-resync-jitter-2-initial-sync-time) + INIT3=$(cat /tmp/network-resync-jitter-3-initial-sync-time) + + CUR1=$(kubectl get network.openstack.k-orc.cloud network-resync-jitter-1 \ + -n ${NAMESPACE} -o jsonpath='{.status.lastSyncTime}') + CUR2=$(kubectl get network.openstack.k-orc.cloud network-resync-jitter-2 \ + -n ${NAMESPACE} -o jsonpath='{.status.lastSyncTime}') + CUR3=$(kubectl get network.openstack.k-orc.cloud network-resync-jitter-3 \ + -n ${NAMESPACE} -o jsonpath='{.status.lastSyncTime}') + + to_epoch_ns() { + date -u -d "$1" +%s%N + } + + DELTA1=$(( $(to_epoch_ns "${CUR1}") - $(to_epoch_ns "${INIT1}") )) + DELTA2=$(( $(to_epoch_ns "${CUR2}") - $(to_epoch_ns "${INIT2}") )) + DELTA3=$(( $(to_epoch_ns "${CUR3}") - $(to_epoch_ns "${INIT3}") )) + + # All three must update, and at least one observed interval must differ. + [ "${CUR1}" != "${INIT1}" ] && \ + [ "${CUR2}" != "${INIT2}" ] && \ + [ "${CUR3}" != "${INIT3}" ] && \ + { [ "${DELTA1}" -ne "${DELTA2}" ] || \ + [ "${DELTA1}" -ne "${DELTA3}" ] || \ + [ "${DELTA2}" -ne "${DELTA3}" ]; } diff --git a/internal/controllers/network/tests/network-resync-jitter/01-record-sync-times.yaml b/internal/controllers/network/tests/network-resync-jitter/01-record-sync-times.yaml new file mode 100644 index 000000000..9ff679a17 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-jitter/01-record-sync-times.yaml @@ -0,0 +1,18 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +# Record the current lastSyncTime for all three networks so that the following +# assert can detect when each network has been independently re-reconciled. +commands: + - script: | + kubectl get network.openstack.k-orc.cloud network-resync-jitter-1 \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.lastSyncTime}' \ + > /tmp/network-resync-jitter-1-initial-sync-time + kubectl get network.openstack.k-orc.cloud network-resync-jitter-2 \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.lastSyncTime}' \ + > /tmp/network-resync-jitter-2-initial-sync-time + kubectl get network.openstack.k-orc.cloud network-resync-jitter-3 \ + -n ${NAMESPACE} \ + -o jsonpath='{.status.lastSyncTime}' \ + > /tmp/network-resync-jitter-3-initial-sync-time diff --git a/internal/controllers/network/tests/network-resync-jitter/README.md b/internal/controllers/network/tests/network-resync-jitter/README.md new file mode 100644 index 000000000..240fa9d37 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-jitter/README.md @@ -0,0 +1,24 @@ +# Network resync with jitter + +## Step 00 + +Create three networks that all share the same `resyncPeriod` (10s). Once all +three become available, each will have a `lastSyncTime` that records when the +controller last successfully reconciled them. + +## Step 01 + +Record the initial `lastSyncTime` for all three networks. + +## Step 02 + +After the resync period elapses, verify that the three recorded timestamps have +advanced and that at least one elapsed interval differs. This confirms both +periodic resync and jittered scheduling for multiple resources using the same +period without requiring every random jitter sample to be unique. + +## Reference + +Tests periodic resync scheduling and jitter: resources with the same +`resyncPeriod` should be independently scheduled rather than all reconciling +simultaneously. diff --git a/internal/controllers/network/tests/network-resync-terminal-error/00-assert.yaml b/internal/controllers/network/tests/network-resync-terminal-error/00-assert.yaml new file mode 100644 index 000000000..3788843dd --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-error-external-1 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-error-external-2 +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/network/tests/network-resync-terminal-error/00-create-resources.yaml b/internal/controllers/network/tests/network-resync-terminal-error/00-create-resources.yaml new file mode 100644 index 000000000..76142252d --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/00-create-resources.yaml @@ -0,0 +1,26 @@ +--- +# Create two networks with the same description so that an import filter +# matching on that description will be ambiguous (multiple results). +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-error-external-1 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Network from "resync-terminal-error" test +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-error-external-2 +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Network from "resync-terminal-error" test diff --git a/internal/controllers/network/tests/network-resync-terminal-error/00-secret.yaml b/internal/controllers/network/tests/network-resync-terminal-error/00-secret.yaml new file mode 100644 index 000000000..f0fb63e85 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/00-secret.yaml @@ -0,0 +1,5 @@ +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/network/tests/network-resync-terminal-error/01-assert.yaml b/internal/controllers/network/tests/network-resync-terminal-error/01-assert.yaml new file mode 100644 index 000000000..c565120a1 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/01-assert.yaml @@ -0,0 +1,29 @@ +--- +# Verify the import fails with a terminal error (InvalidConfiguration) and +# that lastSyncTime is NOT set (no successful reconciliation occurred). +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-resync-terminal-error + ref: network +assertAll: + # lastSyncTime must NOT be set: the reconciliation never succeeded, so + # there is no time of last successful sync to record. + - celExpr: "!has(network.status.lastSyncTime)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-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/network/tests/network-resync-terminal-error/01-import-resource.yaml b/internal/controllers/network/tests/network-resync-terminal-error/01-import-resource.yaml new file mode 100644 index 000000000..e4f9360f7 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/01-import-resource.yaml @@ -0,0 +1,20 @@ +--- +# Attempt to import a network with a filter matching both external networks. +# This will result in a terminal error (InvalidConfiguration) because the +# filter is ambiguous. A resyncPeriod is set to verify that the terminal error +# prevents the resync scheduler from enqueuing further reconciliations. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + # resyncPeriod is configured so we can verify it is suppressed by the + # terminal error (the scheduler must not fire for a resource in this state). + resyncPeriod: 10s + import: + filter: + description: Network from "resync-terminal-error" test diff --git a/internal/controllers/network/tests/network-resync-terminal-error/02-assert.yaml b/internal/controllers/network/tests/network-resync-terminal-error/02-assert.yaml new file mode 100644 index 000000000..0fc02e90b --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/02-assert.yaml @@ -0,0 +1,29 @@ +--- +# After waiting longer than the configured resyncPeriod, the resource must +# still be in the terminal error state and lastSyncTime must still be absent. +# This confirms that the periodic resync scheduler correctly skips resources +# that have a terminal error. +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Network + name: network-resync-terminal-error + ref: network +assertAll: + - celExpr: "!has(network.status.lastSyncTime)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: network-resync-terminal-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/network/tests/network-resync-terminal-error/02-check-no-resync.yaml b/internal/controllers/network/tests/network-resync-terminal-error/02-check-no-resync.yaml new file mode 100644 index 000000000..d43838ba0 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/02-check-no-resync.yaml @@ -0,0 +1,9 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +# Wait for longer than the configured resyncPeriod (10s) to verify that the +# terminal error state prevents the resync scheduler from firing. +# If the scheduler incorrectly fires, the controller would reconcile again, +# potentially changing the condition message or setting lastSyncTime. +commands: + - script: | + sleep 15 diff --git a/internal/controllers/network/tests/network-resync-terminal-error/README.md b/internal/controllers/network/tests/network-resync-terminal-error/README.md new file mode 100644 index 000000000..990d9cb89 --- /dev/null +++ b/internal/controllers/network/tests/network-resync-terminal-error/README.md @@ -0,0 +1,30 @@ +# Terminal error resources don't resync + +## Step 00 + +Create two networks with identical descriptions so that an import filter +matching on that description will find multiple results. + +## Step 01 + +Attempt to import a network using a filter that matches both of the networks +created in step 00. This causes a terminal error (InvalidConfiguration) because +the import is ambiguous: the controller found more than one matching resource. + +Also configure `resyncPeriod: 10s` on the failing resource to verify that the +terminal error state prevents the resync scheduler from enqueuing additional +reconciliations. + +## Step 02 + +Wait 15 seconds (longer than the configured resyncPeriod) and verify that the +resource remains in the terminal error state. Specifically: +- Conditions still show InvalidConfiguration (terminal error unchanged). +- `lastSyncTime` is NOT set, because no successful reconciliation has occurred. +- The resource has NOT been re-reconciled (if resync fired, it might clear the + error or change the condition message). + +## Reference + +Tests that resources in a terminal error state are excluded from the periodic +resync scheduler. diff --git a/test/apivalidations/routerinterface_test.go b/test/apivalidations/routerinterface_test.go index 85ad56c13..efcad5eed 100644 --- a/test/apivalidations/routerinterface_test.go +++ b/test/apivalidations/routerinterface_test.go @@ -18,10 +18,12 @@ package apivalidations import ( "context" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" @@ -96,19 +98,36 @@ var _ = Describe("ORC RouterInterface API validations", func() { Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("subnetRef is required when type is 'Subnet'"))) }) - It("should be immutable", func(ctx context.Context) { + It("should keep identity fields immutable", func(ctx context.Context) { ri := routerInterfaceStub(namespace) patch := baseRouterInterfacePatch(ri) patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). WithType(orcv1alpha1.RouterInterfaceTypeSubnet). WithRouterRef("router-a"). - WithSubnetRef("subnet-a")) + WithSubnetRef("subnet-a"). + WithResyncPeriod(metav1.Duration{Duration: 10 * time.Minute})) Expect(applyObj(ctx, ri, patch)).To(Succeed()) + patch = baseRouterInterfacePatch(ri) patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). WithType(orcv1alpha1.RouterInterfaceTypeSubnet). WithRouterRef("router-b"). WithSubnetRef("subnet-a")) - Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("RouterInterfaceResourceSpec is immutable"))) + Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("routerRef is immutable"))) + + patch = baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithRouterRef("router-a"). + WithSubnetRef("subnet-b")) + Expect(applyObj(ctx, ri, patch)).To(MatchError(ContainSubstring("subnetRef is immutable"))) + + patch = baseRouterInterfacePatch(ri) + patch.WithSpec(applyconfigv1alpha1.RouterInterfaceSpec(). + WithType(orcv1alpha1.RouterInterfaceTypeSubnet). + WithRouterRef("router-a"). + WithSubnetRef("subnet-a"). + WithResyncPeriod(metav1.Duration{Duration: time.Hour})) + Expect(applyObj(ctx, ri, patch)).To(Succeed()) }) }) From a8901c5ab7177096a914c21cce097b49f3f3c5f7 Mon Sep 17 00:00:00 2001 From: eshulman2 Date: Wed, 8 Jul 2026 17:30:40 +0300 Subject: [PATCH 232/237] Document drift detection and external deletion Update the enhancement proposal and user guide to describe periodic drift detection, global and per-resource configuration, jitter, terminal-error behavior, and external deletion semantics. Refresh generated godoc and navigation so the new reconcile status and generic controller interfaces are reflected in the development documentation. --- enhancements/drift-detection.md | 13 +- .../development/godoc/generic-interfaces.md | 28 +++- .../development/godoc/reconcile-status.md | 70 ++++++--- website/docs/user-guide/drift-detection.md | 146 ++++++++++++++++++ website/docs/user-guide/index.md | 9 ++ website/mkdocs.yml | 4 +- 6 files changed, 238 insertions(+), 32 deletions(-) create mode 100644 website/docs/user-guide/drift-detection.md diff --git a/enhancements/drift-detection.md b/enhancements/drift-detection.md index 8c2196907..d63302a8c 100644 --- a/enhancements/drift-detection.md +++ b/enhancements/drift-detection.md @@ -2,10 +2,10 @@ | Field | Value | |-------|-------| -| **Status** | implementable | +| **Status** | implemented | | **Author(s)** | @eshulman | | **Created** | 2026-02-03 | -| **Last Updated** | 2026-02-03 | +| **Last Updated** | 2026-07-05 | | **Tracking Issue** | TBD | ## Summary @@ -190,10 +190,11 @@ When a resource with `managementPolicy=managed` is deleted from OpenStack but th Currently, `GetOrCreateOSResource` returns a terminal error when fetching a resource by `status.id` results in a 404. To support resource recreation, this logic must be updated to: 1. Check if `managementPolicy == managed` and the resource was not imported (no `importID` or `importFilter`) -2. If both conditions are met, clear `status.id` and proceed to the creation path instead of returning an error +2. If both conditions are met, return a typed `ExternallyDeleted` signal via `ReconcileStatus` so the caller can clear `status.id` and trigger recreation on the next reconcile 3. If the resource was imported or is unmanaged, retain the existing terminal error behavior +4. If `GetOSResourceByID` returns a nil resource with no error, return an explicit error rather than silently misinterpreting the invalid actuator response as external deletion -This ensures that managed resources created by ORC are automatically recreated, while imported or unmanaged resources correctly fail with a terminal error when deleted externally. +This ensures that managed resources created by ORC are automatically recreated, while imported or unmanaged resources correctly fail with a terminal error when deleted externally. The typed signal keeps the external-deletion path distinct from invalid actuator responses. **Behavior when drift detection is disabled** (`resyncPeriod: 0`): Periodic resyncs do not occur, so discovery of external deletion depends on other triggers (spec change, controller restart). When discovered, ORC will still recreate managed resources (not a terminal error). The difference is timing of discovery, not the recreation behavior itself. @@ -227,7 +228,7 @@ Drift detection covers all **mutable fields** that ORC actuators implement updat **Mitigation**: - Disabled by default; when enabled, recommend conservative intervals (e.g., 10 hours) -- Add random jitter to resync times to avoid thundering herd: since reconciliation already uses "requeue after X duration", jitter simply adds a random offset (e.g., ±10%) to the resync period, spreading resyncs over time rather than having them fire simultaneously +- Add random jitter to resync times to avoid thundering herd: since reconciliation already uses "requeue after X duration", jitter simply adds a random offset (e.g., [0%, +20%]) to the resync period, spreading resyncs over time rather than having them fire simultaneously - Allow operators to disable or lengthen resync for stable resources ### Controller Resource Consumption @@ -275,3 +276,5 @@ Implement a watcher that periodically lists all resources from OpenStack and com ## Implementation History - 2026-02-03: Enhancement proposed +- 2026-07-05: Addition of explicit `ExternallyDeleted` reconcile status +- 2026-07-05: Initial implementation diff --git a/website/docs/development/godoc/generic-interfaces.md b/website/docs/development/godoc/generic-interfaces.md index 51536dfde..817983747 100644 --- a/website/docs/development/godoc/generic-interfaces.md +++ b/website/docs/development/godoc/generic-interfaces.md @@ -16,6 +16,7 @@ import "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/g - [type ORCApplyConfig](<#ORCApplyConfig>) - [type ORCStatusApplyConfig](<#ORCStatusApplyConfig>) - [type ORCStatusApplyConfigWithID](<#ORCStatusApplyConfigWithID>) +- [type ORCStatusApplyConfigWithLastSyncTime](<#ORCStatusApplyConfigWithLastSyncTime>) - [type ReconcileResourceActuator](<#ReconcileResourceActuator>) - [type ResourceController](<#ResourceController>) - [type ResourceHelperFactory](<#ResourceHelperFactory>) @@ -24,7 +25,7 @@ import "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/g -## type [APIObjectAdapter]() +## type [APIObjectAdapter]() @@ -40,6 +41,8 @@ type APIObjectAdapter[orcObjectPT any, resourceSpecT any, filterT any] interface GetManagementPolicy() orcv1alpha1.ManagementPolicy GetManagedOptions() *orcv1alpha1.ManagedOptions + GetResyncPeriod() *metav1.Duration + GetLastSyncTime() *metav1.Time GetStatusID() *string GetResourceSpec() *resourceSpecT @@ -91,7 +94,7 @@ type BaseResourceActuator[ ``` -## type [Controller]() +## type [Controller]() @@ -99,6 +102,7 @@ type BaseResourceActuator[ type Controller interface { SetupWithManager(context.Context, ctrl.Manager, controller.Options) error GetName() string + SetDefaultResyncPeriod(time.Duration) } ``` @@ -215,9 +219,9 @@ type ORCStatusApplyConfig[statusApplyPT any] interface { ``` -## type [ORCStatusApplyConfigWithID]() +## type [ORCStatusApplyConfigWithID]() -ORCStatusApplyConfigWithID extends ORCStatusApplyConfig with an ID field. This is required by resources that have an OpenStack\-assigned ID stored in status.id. Resources without an ID \(e.g. relationship resources like RoleAssignment\) use only ORCStatusApplyConfig. +ORCStatusApplyConfigWithID extends ORCStatusApplyConfigWithLastSyncTime with an ID field. This is required by resources that have an OpenStack\-assigned ID stored in status.id. Resources without an ID \(e.g. relationship resources like RoleAssignment\) use only ORCStatusApplyConfig. ```go type ORCStatusApplyConfigWithID[statusApplyPT any] interface { @@ -226,6 +230,18 @@ type ORCStatusApplyConfigWithID[statusApplyPT any] interface { } ``` + +## type [ORCStatusApplyConfigWithLastSyncTime]() + +ORCStatusApplyConfigWithLastSyncTime extends ORCStatusApplyConfig with a LastSyncTime field. + +```go +type ORCStatusApplyConfigWithLastSyncTime[statusApplyPT any] interface { + WithLastSyncTime(metav1.Time) statusApplyPT + // contains filtered or unexported methods +} +``` + ## type [ReconcileResourceActuator]() @@ -257,7 +273,7 @@ type ReconcileResourceActuator[orcObjectPT, osResourceT any] interface { ``` -## type [ResourceController]() +## type [ResourceController]() @@ -325,7 +341,7 @@ type ResourceReconciler[orcObjectPT, osResourceT any] func(ctx context.Context, ``` -## type [ResourceStatusWriter]() +## type [ResourceStatusWriter]() ResourceStatusWriter defines methods for writing an ORC object status diff --git a/website/docs/development/godoc/reconcile-status.md b/website/docs/development/godoc/reconcile-status.md index 92bf7c3c3..1205cd504 100644 --- a/website/docs/development/godoc/reconcile-status.md +++ b/website/docs/development/godoc/reconcile-status.md @@ -9,15 +9,18 @@ import "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/g ## Index - [type ReconcileStatus](<#ReconcileStatus>) + - [func ExternallyDeleted\(\) ReconcileStatus](<#ExternallyDeleted>) - [func NeedsRefresh\(\) ReconcileStatus](<#NeedsRefresh>) - [func NewReconcileStatus\(\) ReconcileStatus](<#NewReconcileStatus>) - [func WaitingOnFinalizer\(finalizer string\) ReconcileStatus](<#WaitingOnFinalizer>) - [func WaitingOnObject\(kind, name string, waitingOn WaitingOnEvent\) ReconcileStatus](<#WaitingOnObject>) - [func WaitingOnOpenStack\(waitingOn WaitingOnEvent, pollingPeriod time.Duration\) ReconcileStatus](<#WaitingOnOpenStack>) - [func WrapError\(err error\) ReconcileStatus](<#WrapError>) + - [func \(r ReconcileStatus\) ExternallyDeleted\(\) ReconcileStatus](<#ReconcileStatus.ExternallyDeleted>) - [func \(r ReconcileStatus\) GetError\(\) error](<#ReconcileStatus.GetError>) - [func \(r ReconcileStatus\) GetProgressMessages\(\) \[\]string](<#ReconcileStatus.GetProgressMessages>) - [func \(r ReconcileStatus\) GetRequeue\(\) time.Duration](<#ReconcileStatus.GetRequeue>) + - [func \(r ReconcileStatus\) IsExternallyDeleted\(\) bool](<#ReconcileStatus.IsExternallyDeleted>) - [func \(r ReconcileStatus\) NeedsRefresh\(\) ReconcileStatus](<#ReconcileStatus.NeedsRefresh>) - [func \(r ReconcileStatus\) NeedsReschedule\(\) \(bool, error\)](<#ReconcileStatus.NeedsReschedule>) - [func \(r ReconcileStatus\) Return\(log logr.Logger\) \(ctrl.Result, error\)](<#ReconcileStatus.Return>) @@ -44,8 +47,17 @@ You MUST use the return value of any method which returns ReconcileStatus: the r type ReconcileStatus = *reconcileStatus ``` + +### func [ExternallyDeleted]() + +```go +func ExternallyDeleted() ReconcileStatus +``` + +ExternallyDeleted is a convenience method which returns a new ReconcileStatus with ExternallyDeleted. + -### func [NeedsRefresh]() +### func [NeedsRefresh]() ```go func NeedsRefresh() ReconcileStatus @@ -54,7 +66,7 @@ func NeedsRefresh() ReconcileStatus NeedsRefresh is a convenience method which returns a new ReconcileStatus with NeedsRefresh. -### func [NewReconcileStatus]() +### func [NewReconcileStatus]() ```go func NewReconcileStatus() ReconcileStatus @@ -63,7 +75,7 @@ func NewReconcileStatus() ReconcileStatus NewReconcileStatus returns an empty ReconcileStatus -### func [WaitingOnFinalizer]() +### func [WaitingOnFinalizer]() ```go func WaitingOnFinalizer(finalizer string) ReconcileStatus @@ -72,7 +84,7 @@ func WaitingOnFinalizer(finalizer string) ReconcileStatus WaitingOnFinalizer is a convenience method which returns a new ReconcileStatus with WaitingOnFinalizer. -### func [WaitingOnObject]() +### func [WaitingOnObject]() ```go func WaitingOnObject(kind, name string, waitingOn WaitingOnEvent) ReconcileStatus @@ -81,7 +93,7 @@ func WaitingOnObject(kind, name string, waitingOn WaitingOnEvent) ReconcileStatu WaitingOnObject is a convenience method which returns a new ReconcileStatus with WaitingOnObject. -### func [WaitingOnOpenStack]() +### func [WaitingOnOpenStack]() ```go func WaitingOnOpenStack(waitingOn WaitingOnEvent, pollingPeriod time.Duration) ReconcileStatus @@ -90,7 +102,7 @@ func WaitingOnOpenStack(waitingOn WaitingOnEvent, pollingPeriod time.Duration) R WaitingOnOpenStack is a convenience method which returns a new ReconcileStatus with WaitingOnOpenStack. -### func [WrapError]() +### func [WrapError]() ```go func WrapError(err error) ReconcileStatus @@ -98,8 +110,17 @@ func WrapError(err error) ReconcileStatus WrapError returns a ReconcileStatus containing the given error + +### func \(ReconcileStatus\) [ExternallyDeleted]() + +```go +func (r ReconcileStatus) ExternallyDeleted() ReconcileStatus +``` + +ExternallyDeleted returns a ReconcileStatus indicating that the OpenStack resource referenced by status.id has been deleted outside of ORC. The caller is expected to clear status.id and add an appropriate progress message. + -### func \(ReconcileStatus\) [GetError]() +### func \(ReconcileStatus\) [GetError]() ```go func (r ReconcileStatus) GetError() error @@ -108,7 +129,7 @@ func (r ReconcileStatus) GetError() error GetError returns an error representing all errors which have been added to this ReconcileStatus. If multiple errors have been added they will have been combined with errors.Join\(\) -### func \(ReconcileStatus\) [GetProgressMessages]() +### func \(ReconcileStatus\) [GetProgressMessages]() ```go func (r ReconcileStatus) GetProgressMessages() []string @@ -117,7 +138,7 @@ func (r ReconcileStatus) GetProgressMessages() []string GetProgressMessages returns all progress messages which have been added to this ReconcileStatus -### func \(ReconcileStatus\) [GetRequeue]() +### func \(ReconcileStatus\) [GetRequeue]() ```go func (r ReconcileStatus) GetRequeue() time.Duration @@ -125,8 +146,17 @@ func (r ReconcileStatus) GetRequeue() time.Duration GetRequeue returns the time after which the current object should be reconciled again. A value of 0 indicates that no requeue is requested. + +### func \(ReconcileStatus\) [IsExternallyDeleted]() + +```go +func (r ReconcileStatus) IsExternallyDeleted() bool +``` + +IsExternallyDeleted returns true if the ReconcileStatus indicates that the OpenStack resource was deleted externally. + -### func \(ReconcileStatus\) [NeedsRefresh]() +### func \(ReconcileStatus\) [NeedsRefresh]() ```go func (r ReconcileStatus) NeedsRefresh() ReconcileStatus @@ -135,7 +165,7 @@ func (r ReconcileStatus) NeedsRefresh() ReconcileStatus NeedsRefresh indicates that the resource status needs to be refreshed. It sets an appropriate progress message and ensures that the object will be reconciled again immediately. -### func \(ReconcileStatus\) [NeedsReschedule]() +### func \(ReconcileStatus\) [NeedsReschedule]() ```go func (r ReconcileStatus) NeedsReschedule() (bool, error) @@ -146,7 +176,7 @@ NeedsReschedule returns a boolean value indicating whether the ReconcileStatus w NeedsReschedule is used to shortcut reconciliation if any precondition has not been met. -### func \(ReconcileStatus\) [Return]() +### func \(ReconcileStatus\) [Return]() ```go func (r ReconcileStatus) Return(log logr.Logger) (ctrl.Result, error) @@ -157,7 +187,7 @@ Return returns the the \(ctrl.Result, error\) expected by controller\-runtime fo If a ReconcileStatus contains a TerminalError, Return will log the error directly instead of returning it to controller\-runtime, as this would cause an undesirable reschedule. -### func \(ReconcileStatus\) [WaitingOnFinalizer]() +### func \(ReconcileStatus\) [WaitingOnFinalizer]() ```go func (r ReconcileStatus) WaitingOnFinalizer(finalizer string) ReconcileStatus @@ -166,7 +196,7 @@ func (r ReconcileStatus) WaitingOnFinalizer(finalizer string) ReconcileStatus WaitingOnFinalizer adds a progress message indicating that we are waiting for a specific finalizer to be removed. -### func \(ReconcileStatus\) [WaitingOnObject]() +### func \(ReconcileStatus\) [WaitingOnObject]() ```go func (r ReconcileStatus) WaitingOnObject(kind, name string, waitingOn WaitingOnEvent) ReconcileStatus @@ -175,7 +205,7 @@ func (r ReconcileStatus) WaitingOnObject(kind, name string, waitingOn WaitingOnE WaitingOnObject adds a progress message indicating that we are waiting on a kubernetes object of type kind with name. We expect the controller to have an appropriate watch and handler for this event, so WaitingOnObject does not add an explicit requeue. -### func \(ReconcileStatus\) [WaitingOnOpenStack]() +### func \(ReconcileStatus\) [WaitingOnOpenStack]() ```go func (r ReconcileStatus) WaitingOnOpenStack(waitingOn WaitingOnEvent, pollingPeriod time.Duration) ReconcileStatus @@ -184,7 +214,7 @@ func (r ReconcileStatus) WaitingOnOpenStack(waitingOn WaitingOnEvent, pollingPer WaitingOnOpenStack indicates that we are waiting for an event on the current OpenStack resource. It adds an appropriate progress message. It also adds a requeue with the requested polling period, as we are not able to receive triggers for OpenStack events. -### func \(ReconcileStatus\) [WithError]() +### func \(ReconcileStatus\) [WithError]() ```go func (r ReconcileStatus) WithError(err error) ReconcileStatus @@ -193,7 +223,7 @@ func (r ReconcileStatus) WithError(err error) ReconcileStatus WithError returns a ReconcileStatus containing the given error joined to any existing errors. -### func \(ReconcileStatus\) [WithProgressMessage]() +### func \(ReconcileStatus\) [WithProgressMessage]() ```go func (r ReconcileStatus) WithProgressMessage(msgs ...string) ReconcileStatus @@ -202,7 +232,7 @@ func (r ReconcileStatus) WithProgressMessage(msgs ...string) ReconcileStatus WithProgressMessage returns a ReconcileStatus with the given progress messages in addition to any already present. -### func \(ReconcileStatus\) [WithReconcileStatus]() +### func \(ReconcileStatus\) [WithReconcileStatus]() ```go func (r ReconcileStatus) WithReconcileStatus(o ReconcileStatus) ReconcileStatus @@ -211,7 +241,7 @@ func (r ReconcileStatus) WithReconcileStatus(o ReconcileStatus) ReconcileStatus WithReconcileStatus returns a ReconcileStatus combining all properties of the given ReconcileStatus. -### func \(ReconcileStatus\) [WithRequeue]() +### func \(ReconcileStatus\) [WithRequeue]() ```go func (r ReconcileStatus) WithRequeue(requeue time.Duration) ReconcileStatus @@ -220,7 +250,7 @@ func (r ReconcileStatus) WithRequeue(requeue time.Duration) ReconcileStatus WithRequeue returns a ReconcileStatus with a request to requeue after the given time. If the ReconcileStatus already requests a requeue, the returned object will have the lesser of the existing and requested requeues. -## type [WaitingOnEvent]() +## type [WaitingOnEvent]() WaitingOnEvent represents the type of event we are waiting on diff --git a/website/docs/user-guide/drift-detection.md b/website/docs/user-guide/drift-detection.md new file mode 100644 index 000000000..c79d14680 --- /dev/null +++ b/website/docs/user-guide/drift-detection.md @@ -0,0 +1,146 @@ +# Drift Detection and External Deletion Handling + +ORC can periodically reconcile resources to detect and correct configuration drift — changes made to OpenStack resources outside of ORC's control. This feature also detects when managed resources have been deleted directly from OpenStack and recreates them automatically. + +## Enabling Drift Detection + +Drift detection is disabled by default. Enable it per-resource by setting `spec.resyncPeriod`: + +```yaml +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Network +metadata: + name: critical-network +spec: + cloudCredentialsRef: + secretName: openstack-clouds + cloudName: openstack + managementPolicy: managed + resyncPeriod: 1h # Re-check OpenStack every hour + resource: + description: Critical application network +``` + +The `resyncPeriod` field accepts any Go duration string: `10m`, `1h`, `24h`, etc. Very low values can create a high volume of OpenStack API calls. + +**Default:** `0` (disabled). When disabled, ORC only reconciles resources in response to spec changes or controller restarts. + +### Setting a Global Default + +To enable drift detection for all resources without setting `resyncPeriod` on each one, configure the manager's `--default-resync-period` flag: + +```yaml +spec: + containers: + - name: manager + args: + - --default-resync-period=10h +``` + +Per-resource `spec.resyncPeriod` takes precedence over this default when set. + +!!! note + + Conservative resync periods (e.g., `1h` or `10h`) are recommended in production to avoid excessive OpenStack API calls. + +## How It Works + +After a resource reaches a stable state (`Progressing=False`), ORC schedules a reconciliation after the configured `resyncPeriod`. On each resync: + +1. ORC fetches the current state of the OpenStack resource. +2. For **managed** resources: if drift is detected, ORC updates the resource to match the Kubernetes spec. +3. For **unmanaged** resources: ORC refreshes `status.resource` to reflect the current OpenStack state, but makes no changes. +4. The next resync is scheduled. + +A small random jitter ([0%, +20%]) is applied to `resyncPeriod` to spread reconciliations and avoid thundering-herd effects. + +!!! note + + Resources in a terminal error state (`Progressing=False` with reason `InvalidConfiguration` or `UnrecoverableError`) are **not** periodically resynced. Terminal errors require manual intervention to resolve. + +## Tracking Sync Status + +Every ORC resource has a `status.lastSyncTime` field that records when ORC last successfully reconciled with OpenStack: + +```bash +kubectl get network critical-network -o jsonpath='{.status.lastSyncTime}' +# 2026-02-03T10:30:00Z +``` + +ORC persists this timestamp in the Kubernetes status. After a controller restart, it uses `lastSyncTime` to determine when the next resync should occur, preventing a thundering herd of reconciliations on startup. + +## External Deletion Handling + +When a resource is deleted directly from OpenStack (bypassing ORC), the behavior depends on how ORC originally obtained the resource. + +### ORC-Created Resources (Managed, Not Imported) + +If you created the resource through ORC's `spec.resource` field, ORC **recreates** it automatically: + +1. ORC detects the resource is missing from OpenStack (the ID stored in `status.id` no longer exists). +2. ORC clears `status.id`. +3. On the next reconcile, ORC creates a new OpenStack resource. +4. The new resource ID is stored in `status.id`. + +The ORC object continues to exist and becomes `Available=True` again once the resource is recreated. + +```yaml +# This type of resource will be recreated if deleted from OpenStack +spec: + managementPolicy: managed + resyncPeriod: 10m # Enable resync to detect deletion quickly + resource: # Resource was created by ORC + description: My application network +``` + +!!! warning + + Recreation produces a new OpenStack resource with a **new ID**. Any OpenStack resources (outside ORC) that referenced the old ID will need to be updated manually. + +### Imported Resources (Terminal Error) + +If you imported an existing resource using `spec.import`, ORC reports a **terminal error** when the resource is deleted from OpenStack: + +- `Available=False` +- `Progressing=False` +- Condition reason: `UnrecoverableError` +- Message: `resource has been deleted from OpenStack` + +ORC does **not** recreate imported resources because it did not create them originally, and recreating a new empty resource would not restore what was lost. + +```yaml +# Unmanaged resources enter terminal error if deleted from OpenStack +spec: + managementPolicy: unmanaged + import: + filter: + name: public # Was imported by filter +``` + +To recover: delete and recreate the ORC object pointing at a newly created OpenStack resource. + +### Summary Table + +| Resource Type | How Obtained | External Deletion Behavior | +|--------------|--------------|---------------------------| +| Managed | `spec.resource` | **Recreated** automatically | +| Unmanaged | `spec.import.*` | **Terminal error** | + +## Implications for Dependent Resources + +OpenStack enforces referential integrity for most resource relationships (e.g., a Network cannot be deleted while Subnets exist). If an external deletion manages to bypass these constraints (e.g., direct database manipulation), the behavior of dependent ORC resources follows these rules: + +### If a Parent Resource Is Recreated + +When a parent resource (e.g., Network) is recreated by ORC, dependent resources that reference it (e.g., Subnets) detect the parent as available again but may encounter errors when OpenStack rejects operations referencing the old parent ID. **Manual intervention may be required** to recreate dependent resources against the new parent. + +### If a Parent Resource Enters Terminal Error + +When a parent resource enters terminal error: + +- **Dependent resources waiting on it** (e.g., a Subnet waiting for its Network): ORC will not proceed — it waits until the parent becomes available again. The dependent is not itself in an error state; it is just waiting. +- **Dependent resources already created**: ORC continues managing them normally. If ORC attempts to update a dependent resource that references a deleted parent in OpenStack, the behavior depends on what OpenStack returns for that operation. + +!!! warning + + If a parent resource is externally deleted in a way that bypasses OpenStack's referential integrity checks, the resulting state may require manual cleanup of both the parent and dependent resources. This is an unusual operational scenario and not specific to drift detection. diff --git a/website/docs/user-guide/index.md b/website/docs/user-guide/index.md index 4945fcdef..d30c84dd3 100644 --- a/website/docs/user-guide/index.md +++ b/website/docs/user-guide/index.md @@ -122,6 +122,15 @@ spec: ipVersion: 4 ``` +### Drift Detection and External Deletion + +ORC can periodically reconcile resources to detect configuration drift and recreate managed resources that are deleted directly from OpenStack. See [Drift Detection](drift-detection.md) for details on: + +- How to enable periodic resync with `spec.resyncPeriod` +- How ORC handles externally deleted resources (recreation vs. terminal error) +- How to verify that recreation occurred by checking `status.id` +- Implications for dependent resources + ### Understanding Status and Conditions Every ORC resource reports its status through two conditions: `Available` (whether the resource is ready for use) and `Progressing` (whether ORC is still working on it). For detailed information about conditions and their meanings, see [Troubleshooting: Status Conditions Explained](../troubleshooting.md#status-conditions-explained). diff --git a/website/mkdocs.yml b/website/mkdocs.yml index e71fbe9e9..01f6d8815 100644 --- a/website/mkdocs.yml +++ b/website/mkdocs.yml @@ -7,7 +7,9 @@ nav: - Getting Started: - Installation: installation.md - Quick Start: getting-started.md - - User Guide: user-guide/index.md + - User Guide: + - Overview: user-guide/index.md + - Drift Detection: user-guide/drift-detection.md - CRD Reference: crd-reference.md - Troubleshooting: troubleshooting.md - Contributing: From bd1787d0450952c5421bc82b61d762176fb8144c Mon Sep 17 00:00:00 2001 From: Winston Chen Date: Thu, 16 Jul 2026 13:36:36 +0100 Subject: [PATCH 233/237] Fix endless update when MAC is empty in AllowedAddressPairs --- internal/controllers/port/actuator.go | 4 ++-- internal/controllers/port/actuator_test.go | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index a602d2d69..0ff40ba21 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -506,7 +506,7 @@ func handleAllowedAddressPairsUpdate(updateOpts *ports.UpdateOpts, resource *orc for _, desired := range desiredPairs { found := false for _, actual := range osResource.AllowedAddressPairs { - if actual.IPAddress == desired.IPAddress && actual.MACAddress == desired.MACAddress { + if actual.IPAddress == desired.IPAddress && (desired.MACAddress == "" || actual.MACAddress == desired.MACAddress) { found = true break } @@ -521,7 +521,7 @@ func handleAllowedAddressPairsUpdate(updateOpts *ports.UpdateOpts, resource *orc for _, actual := range osResource.AllowedAddressPairs { found := false for _, desired := range desiredPairs { - if actual.IPAddress == desired.IPAddress && actual.MACAddress == desired.MACAddress { + if actual.IPAddress == desired.IPAddress && (desired.MACAddress == "" || actual.MACAddress == desired.MACAddress) { found = true break } diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index 84d12768f..4f9a5adc7 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -206,6 +206,28 @@ func TestHandleAllowedAddressPairsUpdate(t *testing.T) { }, expectChange: true, }, + { + name: "Entry with empty MAC address", + newValue: []orcv1alpha1.AllowedAddressPair{ + {IP: orcv1alpha1.IPvAny("192.168.100.1")}, + }, + existingValue: []ports.AddressPair{ + {IPAddress: "192.168.100.1", MACAddress: "00:1A:2B:3C:4D:5E"}, + }, + expectChange: false, + }, + { + name: "Entries with empty and filled MAC addresses", + newValue: []orcv1alpha1.AllowedAddressPair{ + {IP: orcv1alpha1.IPvAny("192.168.100.1")}, + {IP: orcv1alpha1.IPvAny("192.168.200.1"), MAC: ptr.To(orcv1alpha1.MAC("00:1A:2B:3C:4D:6E"))}, + }, + existingValue: []ports.AddressPair{ + {IPAddress: "192.168.100.1", MACAddress: "00:1A:2B:3C:4D:5E"}, + {IPAddress: "192.168.200.1", MACAddress: "00:1A:2B:3C:4D:6E"}, + }, + expectChange: false, + }, } for _, tt := range testCases { From 3406c335734792ae4fbf565867511c1056ab3319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Tue, 21 Jul 2026 10:37:49 +0900 Subject: [PATCH 234/237] test: reduce possibility of namespace collision in unit tests Replace `GenerateName()` with client-side name generation using `utilrand.String(10)` for test namespaces. When using `GenerateName()`, it generates a new 5-char random suffix and the API server checks if that name exists. Envtest only runs kube-apiserver + etcd so namespaces marked for deletion are never actually cleaned up. On Kubernetes 1.29 (our envtest version, chosen as the minimum for CEL validation), the `RetryGenerateName` feature gate [1] doesn't exist yet (introduced in 1.30), so there are no retries and the collision is a hard failure. By setting Name directly with a locally-generated 10-char random string, we considerably increase the pool of possible names, making collisions practically impossible. [1] https://www.kubernetes.dev/resources/keps/4420/ Closes: #850 --- internal/controllers/floatingip/suite_test.go | 3 ++- internal/controllers/image/suite_test.go | 3 ++- internal/controllers/image/upload_test.go | 3 ++- internal/controllers/router/suite_test.go | 3 ++- internal/controllers/routerinterface/suite_test.go | 3 ++- internal/controllers/subnet/suite_test.go | 3 ++- test/apivalidations/suite_test.go | 4 +++- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/controllers/floatingip/suite_test.go b/internal/controllers/floatingip/suite_test.go index f1a7eb8fa..2494c4c0e 100644 --- a/internal/controllers/floatingip/suite_test.go +++ b/internal/controllers/floatingip/suite_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,7 +83,7 @@ var _ = Describe("EnvTest sanity check", func() { It("should be able to create a namespace", func() { ctx := context.TODO() namespace := &corev1.Namespace{} - namespace.SetGenerateName("test-") + namespace.SetName("test-" + utilrand.String(10)) // Create the namespace Expect(k8sClient.Create(ctx, namespace)).To(Succeed(), "create namespace") diff --git a/internal/controllers/image/suite_test.go b/internal/controllers/image/suite_test.go index 4b1fa4de2..5803d580f 100644 --- a/internal/controllers/image/suite_test.go +++ b/internal/controllers/image/suite_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,7 +83,7 @@ var _ = Describe("EnvTest sanity check", func() { It("should be able to create a namespace", func() { ctx := context.TODO() namespace := &corev1.Namespace{} - namespace.SetGenerateName("test-") + namespace.SetName("test-" + utilrand.String(10)) // Create the namespace Expect(k8sClient.Create(ctx, namespace)).To(Succeed(), "create namespace") diff --git a/internal/controllers/image/upload_test.go b/internal/controllers/image/upload_test.go index cfdb2a5f5..3a2f92565 100644 --- a/internal/controllers/image/upload_test.go +++ b/internal/controllers/image/upload_test.go @@ -28,6 +28,7 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" corev1 "k8s.io/api/core/v1" + utilrand "k8s.io/apimachinery/pkg/util/rand" ctrl "sigs.k8s.io/controller-runtime" orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" @@ -158,7 +159,7 @@ var _ = Describe("Upload tests", Ordered, func() { // Create the namespace namespace = &corev1.Namespace{} - namespace.SetGenerateName("test-") + namespace.SetName("test-" + utilrand.String(10)) Expect(k8sClient.Create(ctx, namespace)).To(Succeed(), "create namespace") DeferCleanup(func() { Expect(k8sClient.Delete(ctx, namespace)).To(Succeed(), "delete namespace") diff --git a/internal/controllers/router/suite_test.go b/internal/controllers/router/suite_test.go index 02433215f..c04e852fe 100644 --- a/internal/controllers/router/suite_test.go +++ b/internal/controllers/router/suite_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,7 +83,7 @@ var _ = Describe("EnvTest sanity check", func() { It("should be able to create a namespace", func() { ctx := context.TODO() namespace := &corev1.Namespace{} - namespace.SetGenerateName("test-") + namespace.SetName("test-" + utilrand.String(10)) // Create the namespace Expect(k8sClient.Create(ctx, namespace)).To(Succeed(), "create namespace") diff --git a/internal/controllers/routerinterface/suite_test.go b/internal/controllers/routerinterface/suite_test.go index 8c93ac434..3bae8c80e 100644 --- a/internal/controllers/routerinterface/suite_test.go +++ b/internal/controllers/routerinterface/suite_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,7 +83,7 @@ var _ = Describe("EnvTest sanity check", func() { It("should be able to create a namespace", func() { ctx := context.TODO() namespace := &corev1.Namespace{} - namespace.SetGenerateName("test-") + namespace.SetName("test-" + utilrand.String(10)) // Create the namespace Expect(k8sClient.Create(ctx, namespace)).To(Succeed(), "create namespace") diff --git a/internal/controllers/subnet/suite_test.go b/internal/controllers/subnet/suite_test.go index 847b93590..ec8821440 100644 --- a/internal/controllers/subnet/suite_test.go +++ b/internal/controllers/subnet/suite_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + utilrand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,7 +83,7 @@ var _ = Describe("EnvTest sanity check", func() { It("should be able to create a namespace", func() { ctx := context.TODO() namespace := &corev1.Namespace{} - namespace.SetGenerateName("test-") + namespace.SetName("test-" + utilrand.String(10)) // Create the namespace Expect(k8sClient.Create(ctx, namespace)).To(Succeed(), "create namespace") diff --git a/test/apivalidations/suite_test.go b/test/apivalidations/suite_test.go index 595c4a95f..98c037168 100644 --- a/test/apivalidations/suite_test.go +++ b/test/apivalidations/suite_test.go @@ -24,6 +24,8 @@ import ( "testing" "time" + utilrand "k8s.io/apimachinery/pkg/util/rand" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -147,7 +149,7 @@ var _ = BeforeSuite(func() { func createNamespace() *corev1.Namespace { By("Creating namespace") namespace := corev1.Namespace{} - namespace.GenerateName = "test-" + namespace.Name = "test-" + utilrand.String(10) Expect(k8sClient.Create(ctx, &namespace)).To(Succeed(), "Namespace creation should succeed") DeferCleanup(func() { By("Deleting namespace") From eec88a054580816c93f7ffc313335080a71c386a Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Tue, 21 Jul 2026 10:53:17 +0100 Subject: [PATCH 235/237] =?UTF-8?q?The=20fix=20matches=20the=20established?= =?UTF-8?q?=20pattern=20from=20the=20securitygroup=20dependency=20test=20?= =?UTF-8?q?=E2=80=94=20CEL=20expressions=20for=20the=20dependency=20being?= =?UTF-8?q?=20deleted=20(avoids=20=20=20racing=20with=20the=20Project=20co?= =?UTF-8?q?ntroller's=20status=20updates),=20and=20standard=20status=20ass?= =?UTF-8?q?ertions=20for=20the=20RoleAssignment=20which=20isn't=20being=20?= =?UTF-8?q?deleted=20and=20has=20stable=20conditions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Lawton --- .../roleassignment-dependency/02-assert.yaml | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml index f1c77d3b5..3473f0837 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml @@ -1,18 +1,15 @@ --- # Verify Project still exists (deletion blocked by finalizer) -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-dep-project - # deletionTimestamp should be set, but resource should still exist -status: - conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-dep-project + ref: project +assertAll: + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in project.metadata.finalizers" --- # Verify RoleAssignment still Available apiVersion: openstack.k-orc.cloud/v1alpha1 From a69bf57d9a8029de682b8ad071c9f8ad69c3a0c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:14:00 +0000 Subject: [PATCH 236/237] :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: [golang.org/x/text](https://github.com/golang/text), [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) and [sigs.k8s.io/structured-merge-diff/v6](https://github.com/kubernetes-sigs/structured-merge-diff). Updates `golang.org/x/text` from 0.38.0 to 0.40.0 - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.38.0...v0.40.0) Updates `k8s.io/apimachinery` from 0.34.9 to 0.34.10 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.9...v0.34.10) Updates `sigs.k8s.io/structured-merge-diff/v6` from 6.4.0 to 6.4.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.4.0...v6.4.2) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-go-mod-patch-and-minor - dependency-name: k8s.io/apimachinery dependency-version: 0.34.10 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.4.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 | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 9914ebd13..e5a843da1 100644 --- a/go.mod +++ b/go.mod @@ -12,16 +12,16 @@ require ( github.com/onsi/gomega v1.42.1 github.com/ulikunitz/xz v0.5.15 go.uber.org/mock v0.6.0 - golang.org/x/text v0.38.0 + golang.org/x/text v0.40.0 k8s.io/api v0.34.9 - k8s.io/apimachinery v0.34.9 + k8s.io/apimachinery v0.34.10 k8s.io/client-go v0.34.9 k8s.io/code-generator v0.34.9 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.4.0 + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 sigs.k8s.io/yaml v1.6.0 ) @@ -84,20 +84,20 @@ 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.36.0 // indirect + golang.org/x/mod v0.37.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.21.0 // indirect + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/term v0.44.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.45.0 // indirect + golang.org/x/tools v0.47.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-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 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // 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 2d3765b95..d8e12d746 100644 --- a/go.sum +++ b/go.sum @@ -207,8 +207,8 @@ 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.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= 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= @@ -220,8 +220,8 @@ golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE 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.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.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= @@ -231,16 +231,16 @@ golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= 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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= 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.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= 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= @@ -259,8 +259,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1: 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= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/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= @@ -275,8 +275,8 @@ k8s.io/api v0.34.9 h1:aVsK5NQL7146suJriGuvpi9giNpwIRSHJ8v5HWakwBo= k8s.io/api v0.34.9/go.mod h1:8oYqD5tLKgvBSnkuDbHZTNrk7NTHybkfYjJ6lNjThjQ= 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.9 h1:WuRPolTfoEST1TQe9sLcT1QAWLMT0CNE4Eqj29XuUmQ= -k8s.io/apimachinery v0.34.9/go.mod h1:z7dd12Xd400CXIycE8nmn32xZhApV9zskHs0A5xeU/Q= +k8s.io/apimachinery v0.34.10 h1:2TkKKtyUGjkdf1fTNEoANuv46QXFIi6UfMfrMxJ9Glg= +k8s.io/apimachinery v0.34.10/go.mod h1:gCxm98KdKjmJKLtGA2OQOIGmb3tY/csRmlQSymG3tLw= 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.9 h1:HlhSEGPyCFH5rQADW6NPKEziGns6ekgUCPK0OsOGU90= @@ -301,7 +301,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.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= -sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.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 da4483add6feec2963d139ccae80763fd2d45c35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:15:20 +0000 Subject: [PATCH 237/237] :seedling:(deps): Bump the all-github-actions group across 1 directory with 5 updates Bumps the all-github-actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `7.0.0` | `7.0.1` | | [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.1.0` | `4.2.0` | | [actions/setup-go](https://github.com/actions/setup-go) | `6.5.0` | `7.0.0` | | [actions/labeler](https://github.com/actions/labeler) | `6.1.0` | `7.0.0` | | [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) | `0.5.7` | `0.6.0` | Updates `actions/checkout` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1) Updates `docker/setup-buildx-action` from 4.1.0 to 4.2.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5...bb05f3f5519dd87d3ba754cc423b652a5edd6d2c) Updates `actions/setup-go` from 6.5.0 to 7.0.0 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/924ae3a1cded613372ab5595356fb5720e22ba16...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e) Updates `actions/labeler` from 6.1.0 to 7.0.0 - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/f27b608878404679385c85cfa523b85ccb86e213...bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13) Updates `zizmorcore/zizmor-action` from 0.5.7 to 0.6.0 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/192e21d79ab29983730a13d1382995c2307fbcaa...6599ee8b7a49aef6a770f63d261d214911a7ce02) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-github-actions - dependency-name: docker/setup-buildx-action dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions - dependency-name: actions/setup-go dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions - dependency-name: actions/labeler dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/container_image.yaml | 4 ++-- .github/workflows/e2e.yaml | 2 +- .github/workflows/ensure-labels.yaml | 2 +- .github/workflows/generate.yaml | 2 +- .github/workflows/go-lint.yaml | 4 ++-- .github/workflows/label-pr.yaml | 2 +- .github/workflows/pr-dependabot.yaml | 4 ++-- .github/workflows/release_image.yaml | 4 ++-- .github/workflows/semver.yaml | 4 ++-- .github/workflows/unit.yml | 4 ++-- .github/workflows/website.yaml | 2 +- .github/workflows/weekly-security-scan.yaml | 4 ++-- .github/workflows/zizmor.yaml | 4 ++-- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/container_image.yaml b/.github/workflows/container_image.yaml index 6e904b175..d970ac680 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # tag=v4.1.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # tag=v4.2.0 - name: Build and push images run: | diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 2922eacdf..3ca389883 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/ensure-labels.yaml b/.github/workflows/ensure-labels.yaml index 822fb5efe..e90a8df12 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c # tag=v1.3.0 diff --git a/.github/workflows/generate.yaml b/.github/workflows/generate.yaml index fa8a506f1..d6ab64616 100644 --- a/.github/workflows/generate.yaml +++ b/.github/workflows/generate.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index ef2269990..cb1bdca4a 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false @@ -22,7 +22,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # tag=v7.0.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/label-pr.yaml b/.github/workflows/label-pr.yaml index 17f57643d..86a109eb7 100644 --- a/.github/workflows/label-pr.yaml +++ b/.github/workflows/label-pr.yaml @@ -81,4 +81,4 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # tag=v6.1.0 + - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # tag=v7.0.0 diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml index db063ac2f..fd4428821 100644 --- a/.github/workflows/pr-dependabot.yaml +++ b/.github/workflows/pr-dependabot.yaml @@ -19,14 +19,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 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 - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # tag=v7.0.0 with: go-version: ${{ steps.vars.outputs.go_version }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 diff --git a/.github/workflows/release_image.yaml b/.github/workflows/release_image.yaml index 9b69ef4ca..c43cb22ed 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 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@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # tag=v4.1.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # tag=v4.2.0 - name: Install build dependencies run: sudo apt-get install -y libgpgme-dev diff --git a/.github/workflows/semver.yaml b/.github/workflows/semver.yaml index 37d67e203..64868723f 100644 --- a/.github/workflows/semver.yaml +++ b/.github/workflows/semver.yaml @@ -13,7 +13,7 @@ jobs: analyze: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -33,7 +33,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # tag=v7.0.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 625b525d2..229302816 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -17,7 +17,7 @@ jobs: - '1' steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false @@ -26,7 +26,7 @@ jobs: run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # tag=v7.0.0 with: go-version: ${{ steps.vars.outputs.go_version }} diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index a4ae0d37f..2400af0d8 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/weekly-security-scan.yaml b/.github/workflows/weekly-security-scan.yaml index a95d4c81e..409580f9f 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: ref: ${{ matrix.branch }} persist-credentials: false @@ -26,7 +26,7 @@ jobs: id: vars run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # tag=v6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # tag=v7.0.0 with: go-version: ${{ steps.vars.outputs.go_version }} - name: Run verify security target diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index fe1b4b3e9..ec149e4d5 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -20,9 +20,9 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # tag=v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # tag=v7.0.1 with: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # tag=v0.5.7 + uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # tag=v0.6.0