From a76b01dd4764ffc0904cce6f7859843468b4d532 Mon Sep 17 00:00:00 2001 From: Fabian Mutzbauer Date: Tue, 9 Jun 2026 14:58:04 +0200 Subject: [PATCH] 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 2e346c070..46cb8f82b 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -2845,6 +2845,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 34d65a029..73b1cfa3c 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 3db974f26..84e90d35a 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1016,6 +1016,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: \{\}
|