Skip to content

Commit ec7d589

Browse files
committed
Address SwiftContainer review feedback
1 parent 13cc828 commit ec7d589

18 files changed

Lines changed: 49 additions & 276 deletions

File tree

api/v1alpha1/swiftcontainer_types.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ package v1alpha1
2020
// and 256 characters long and must not contain forward slashes.
2121
// +kubebuilder:validation:MinLength:=1
2222
// +kubebuilder:validation:MaxLength:=256
23-
// +kubebuilder:validation:Pattern:=`^[^/]+$`
23+
// +kubebuilder:validation:XValidation:rule="!self.contains('/')",message="name must not contain forward slashes"
2424
// +kubebuilder:validation:XValidation:rule="self.size() <= 256",message="name must not exceed 256 UTF-8 bytes"
2525
type SwiftContainerName string
2626

@@ -55,13 +55,9 @@ type SwiftContainerMetadataStatus struct {
5555
Value string `json:"value,omitempty"`
5656
}
5757

58-
// SwiftContainerFilter defines an existing resource by its properties
58+
// SwiftContainerFilter defines an existing resource query.
5959
// +kubebuilder:validation:MinProperties:=1
6060
type SwiftContainerFilter struct {
61-
// name of the existing resource
62-
// +optional
63-
Name *SwiftContainerName `json:"name,omitempty"`
64-
6561
// prefix filters containers by name prefix. Only containers whose names
6662
// begin with this prefix will be considered.
6763
// +kubebuilder:validation:MinLength:=1

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/models-schema/zz_generated.openapi.go

Lines changed: 1 addition & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/openstack.k-orc.cloud_swiftcontainers.yaml

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,6 @@ spec:
9191
error state and will not continue to retry.
9292
minProperties: 1
9393
properties:
94-
name:
95-
description: name of the existing resource
96-
maxLength: 256
97-
minLength: 1
98-
pattern: ^[^/]+$
99-
type: string
100-
x-kubernetes-validations:
101-
- message: name must not exceed 256 UTF-8 bytes
102-
rule: self.size() <= 256
10394
prefix:
10495
description: |-
10596
prefix filters containers by name prefix. Only containers whose names
@@ -115,9 +106,10 @@ spec:
115106
The ORC object will enter an error state if the resource does not exist.
116107
maxLength: 256
117108
minLength: 1
118-
pattern: ^[^/]+$
119109
type: string
120110
x-kubernetes-validations:
111+
- message: name must not contain forward slashes
112+
rule: '!self.contains(''/'')'
121113
- message: name must not exceed 256 UTF-8 bytes
122114
rule: self.size() <= 256
123115
type: object
@@ -212,11 +204,12 @@ spec:
212204
the account and must not contain forward slashes.
213205
maxLength: 256
214206
minLength: 1
215-
pattern: ^[^/]+$
216207
type: string
217208
x-kubernetes-validations:
218209
- message: name is immutable
219210
rule: self == oldSelf
211+
- message: name must not contain forward slashes
212+
rule: '!self.contains(''/'')'
220213
- message: name must not exceed 256 UTF-8 bytes
221214
rule: self.size() <= 256
222215
storagePolicy:

internal/controllers/swiftcontainer/actuator.go

Lines changed: 19 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -104,66 +104,35 @@ func (actuator swiftcontainerActuator) ListOSResourcesForAdoption(ctx context.Co
104104

105105
func (actuator swiftcontainerActuator) ListOSResourcesForImport(ctx context.Context, _ orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
106106
return func(yield func(*osContainerT, error) bool) {
107-
if filter.Name != nil {
108-
name := string(*filter.Name)
109-
// If a prefix filter is also set, verify the name satisfies it.
110-
// Without this check a filter {name: "x", prefix: "y-"} would
111-
// silently import "x" even though it does not match the prefix.
112-
if filter.Prefix != nil && !hasPrefix(name, *filter.Prefix) {
107+
// List all containers and filter by prefix.
108+
listOpts := containers.ListOpts{}
109+
for container, err := range actuator.osClient.ListContainers(ctx, listOpts) {
110+
if err != nil {
111+
yield(nil, err)
113112
return
114113
}
115-
header, err := actuator.osClient.GetContainer(ctx, name, nil)
114+
115+
if filter.Prefix != nil && !strings.HasPrefix(container.Name, *filter.Prefix) {
116+
continue
117+
}
118+
119+
header, err := actuator.osClient.GetContainer(ctx, container.Name, nil)
116120
if err != nil {
117-
if !orcerrors.IsNotFound(err) {
118-
yield(nil, err)
119-
}
121+
yield(nil, err)
120122
return
121123
}
122-
metadata, err := actuator.osClient.GetContainerMetadata(ctx, name)
124+
metadata, err := actuator.osClient.GetContainerMetadata(ctx, container.Name)
123125
if err != nil {
124126
yield(nil, err)
125127
return
126128
}
127-
yield(&osContainerT{Name: name, Metadata: metadata, GetHeader: *header}, nil)
128-
} else {
129-
// List all containers and filter by prefix
130-
listOpts := containers.ListOpts{}
131-
for container, err := range actuator.osClient.ListContainers(ctx, listOpts) {
132-
if err != nil {
133-
yield(nil, err)
134-
return
135-
}
136-
137-
if filter.Prefix != nil && !hasPrefix(container.Name, *filter.Prefix) {
138-
continue
139-
}
140-
141-
header, err := actuator.osClient.GetContainer(ctx, container.Name, nil)
142-
if err != nil {
143-
yield(nil, err)
144-
return
145-
}
146-
metadata, err := actuator.osClient.GetContainerMetadata(ctx, container.Name)
147-
if err != nil {
148-
yield(nil, err)
149-
return
150-
}
151-
if !yield(&osContainerT{Name: container.Name, Metadata: metadata, GetHeader: *header}, nil) {
152-
return
153-
}
129+
if !yield(&osContainerT{Name: container.Name, Metadata: metadata, GetHeader: *header}, nil) {
130+
return
154131
}
155132
}
156133
}, nil
157134
}
158135

159-
// hasPrefix checks if name starts with prefix.
160-
func hasPrefix(name, prefix string) bool {
161-
if len(prefix) > len(name) {
162-
return false
163-
}
164-
return name[:len(prefix)] == prefix
165-
}
166-
167136
func (actuator swiftcontainerActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osContainerT, progress.ReconcileStatus) {
168137
resource := obj.Spec.Resource
169138

@@ -175,35 +144,10 @@ func (actuator swiftcontainerActuator) CreateResource(ctx context.Context, obj o
175144

176145
name := getResourceName(obj)
177146

178-
// Swift treats '/' as a path separator in container names, making the name
179-
// invalid at the API level. Validate explicitly to provide a clear error
180-
// message rather than a confusing HTTP error from gophercloud.
181-
if strings.Contains(name, "/") {
182-
return nil, progress.WrapError(
183-
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration,
184-
"container name must not contain forward slashes"))
185-
}
186-
187-
// Swift limits container names to 256 UTF-8 bytes. The kubebuilder
188-
// MaxLength:=256 marker counts Unicode code points, not bytes, so a name
189-
// with multi-byte UTF-8 characters can pass API validation yet exceed the
190-
// byte limit. Validate explicitly here to produce a clear error message.
191-
if len(name) > 256 {
192-
return nil, progress.WrapError(
193-
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration,
194-
"container name must not exceed 256 bytes"))
195-
}
196-
197-
createOpts := containers.CreateOpts{}
198-
199-
if resource.ContainerRead != "" {
200-
createOpts.ContainerRead = resource.ContainerRead
201-
}
202-
if resource.ContainerWrite != "" {
203-
createOpts.ContainerWrite = resource.ContainerWrite
204-
}
205-
if resource.StoragePolicy != "" {
206-
createOpts.StoragePolicy = resource.StoragePolicy
147+
createOpts := containers.CreateOpts{
148+
ContainerRead: resource.ContainerRead,
149+
ContainerWrite: resource.ContainerWrite,
150+
StoragePolicy: resource.StoragePolicy,
207151
}
208152

209153
if len(resource.Metadata) > 0 {

internal/controllers/swiftcontainer/actuator_test.go

Lines changed: 0 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -197,27 +197,6 @@ func TestListOSResourcesForImport(t *testing.T) {
197197
client osclients.SwiftContainerClient
198198
checks []checkFunc
199199
}{
200-
{
201-
name: "finds one by name",
202-
filter: orcv1alpha1.SwiftContainerFilter{Name: ptr.To[orcv1alpha1.SwiftContainerName]("my-container")},
203-
client: &mockSwiftContainerClient{
204-
containerData: map[string]mockContainerData{
205-
"my-container": {header: containers.GetHeader{}, metadata: map[string]string{}},
206-
"other-container": {header: containers.GetHeader{}, metadata: map[string]string{}},
207-
},
208-
},
209-
checks: checks(noError, findsID("my-container"), findsN(1)),
210-
},
211-
{
212-
name: "finds none by name",
213-
filter: orcv1alpha1.SwiftContainerFilter{Name: ptr.To[orcv1alpha1.SwiftContainerName]("missing-container")},
214-
client: &mockSwiftContainerClient{
215-
containerData: map[string]mockContainerData{
216-
"my-container": {header: containers.GetHeader{}, metadata: map[string]string{}},
217-
},
218-
},
219-
checks: checks(noError, findsN(0)),
220-
},
221200
{
222201
name: "finds multiple containers matching prefix filter",
223202
filter: orcv1alpha1.SwiftContainerFilter{Prefix: ptr.To("test-")},
@@ -243,37 +222,6 @@ func TestListOSResourcesForImport(t *testing.T) {
243222
},
244223
checks: checks(wantError(errTest)),
245224
},
246-
{
247-
// When both Name and Prefix are set and the named container does not
248-
// match the prefix, no results should be returned. Previously the
249-
// prefix predicate was silently ignored in the name-lookup branch.
250-
name: "finds none when name does not match prefix",
251-
filter: orcv1alpha1.SwiftContainerFilter{
252-
Name: ptr.To[orcv1alpha1.SwiftContainerName]("prod-bucket"),
253-
Prefix: ptr.To("test-"),
254-
},
255-
client: &mockSwiftContainerClient{
256-
containerData: map[string]mockContainerData{
257-
"prod-bucket": {header: containers.GetHeader{}, metadata: map[string]string{}},
258-
},
259-
},
260-
checks: checks(noError, findsN(0)),
261-
},
262-
{
263-
// When both Name and Prefix are set and the named container matches
264-
// the prefix, the container should be found normally.
265-
name: "finds one when name matches prefix",
266-
filter: orcv1alpha1.SwiftContainerFilter{
267-
Name: ptr.To[orcv1alpha1.SwiftContainerName]("test-bucket"),
268-
Prefix: ptr.To("test-"),
269-
},
270-
client: &mockSwiftContainerClient{
271-
containerData: map[string]mockContainerData{
272-
"test-bucket": {header: containers.GetHeader{}, metadata: map[string]string{}},
273-
},
274-
},
275-
checks: checks(noError, findsN(1), findsID("test-bucket")),
276-
},
277225
} {
278226
t.Run(tc.name, func(t *testing.T) {
279227
ctx := context.Background()
@@ -491,45 +439,6 @@ func TestCreateResource(t *testing.T) {
491439
}
492440
})
493441

494-
t.Run("returns terminal error for invalid container name containing slash", func(t *testing.T) {
495-
ctx := context.Background()
496-
497-
// The actuator explicitly validates the container name before calling
498-
// the Swift API. A slash in the name is caught early and returned as a
499-
// terminal error with a message mentioning "forward slashes".
500-
// (Kubebuilder validation normally prevents this from reaching the
501-
// controller, but in unit tests API validation is not enforced.)
502-
client := &mockSwiftContainerClient{}
503-
actuator := swiftcontainerActuator{client}
504-
// Use a name that bypasses kubebuilder validation (in unit tests, API
505-
// validation is not enforced); this simulates what would happen if a
506-
// slash somehow reached the actuator.
507-
orcObject := &orcv1alpha1.SwiftContainer{
508-
ObjectMeta: metav1.ObjectMeta{Name: "invalid"},
509-
Spec: orcv1alpha1.SwiftContainerSpec{
510-
Resource: &orcv1alpha1.SwiftContainerResourceSpec{
511-
Name: ptr.To[orcv1alpha1.SwiftContainerName]("invalid/name"),
512-
},
513-
},
514-
}
515-
516-
result, reconcileStatus := actuator.CreateResource(ctx, orcObject)
517-
if result != nil {
518-
t.Errorf("expected nil result, got %v", result)
519-
}
520-
if reconcileStatus == nil {
521-
t.Fatal("expected non-nil reconcile status for terminal error")
522-
}
523-
_, err := reconcileStatus.NeedsReschedule()
524-
if err == nil {
525-
t.Error("expected error from reconcile status")
526-
}
527-
var termErr *orcerrors.TerminalError
528-
if !errors.As(err, &termErr) {
529-
t.Errorf("expected TerminalError, got %T: %v", err, err)
530-
}
531-
})
532-
533442
t.Run("returns terminal error for container name exceeding 256 bytes", func(t *testing.T) {
534443
ctx := context.Background()
535444

@@ -594,42 +503,6 @@ func TestCreateResource(t *testing.T) {
594503
}
595504

596505
func TestContainerNameValidation(t *testing.T) {
597-
t.Run("rejects names containing forward slash", func(t *testing.T) {
598-
name := orcv1alpha1.SwiftContainerName("containers/bucket")
599-
if !strings.Contains(string(name), "/") {
600-
t.Fatal("test setup error: name should contain a slash")
601-
}
602-
// The actuator validates the name before calling the Swift API.
603-
// A slash in the name causes an early terminal error with a message
604-
// mentioning "forward slashes". (Kubebuilder pattern validation would
605-
// normally catch this before it reaches the controller, but in unit
606-
// tests API validation is not enforced.)
607-
ctx := context.Background()
608-
client := &mockSwiftContainerClient{}
609-
actuator := swiftcontainerActuator{client}
610-
orcObject := &orcv1alpha1.SwiftContainer{
611-
ObjectMeta: metav1.ObjectMeta{Name: "invalid"},
612-
Spec: orcv1alpha1.SwiftContainerSpec{
613-
Resource: &orcv1alpha1.SwiftContainerResourceSpec{
614-
Name: ptr.To(name),
615-
},
616-
},
617-
}
618-
_, reconcileStatus := actuator.CreateResource(ctx, orcObject)
619-
if reconcileStatus == nil {
620-
t.Error("expected reconcile status error for name with slash")
621-
return
622-
}
623-
_, err := reconcileStatus.NeedsReschedule()
624-
var termErr *orcerrors.TerminalError
625-
if !errors.As(err, &termErr) {
626-
t.Errorf("expected TerminalError for invalid name, got %T: %v", err, err)
627-
}
628-
if !strings.Contains(termErr.Error(), "forward slashes") {
629-
t.Errorf("expected error message to mention 'forward slashes', got: %v", termErr.Error())
630-
}
631-
})
632-
633506
t.Run("rejects names exceeding 256 UTF-8 bytes", func(t *testing.T) {
634507
longName := orcv1alpha1.SwiftContainerName(strings.Repeat("x", 257))
635508
if len(longName) <= 256 {

internal/controllers/swiftcontainer/tests/.gitkeep

Whitespace-only changes.

internal/controllers/swiftcontainer/tests/swiftcontainer-create-full/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ observed state corresponds to the spec.
77

88
Validates that:
99
- The OpenStack resource uses the name from `spec.resource.name` when it is
10-
specified, rather than the ORC object name (SC-002).
10+
specified, rather than the ORC object name.
1111
- Custom metadata key-value pairs are applied and reflected in
1212
`status.resource.metadata`.
1313
- Read ACL (`containerRead`) and write ACL (`containerWrite`) are configured

internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/00-assert.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ status:
88
name: swiftcontainer-create-minimal
99
bytesUsed: 0
1010
objectCount: 0
11+
storagePolicy: Policy-0
1112
conditions:
1213
- type: Available
1314
status: "True"

internal/controllers/swiftcontainer/tests/swiftcontainer-create-minimal/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Create a minimal SwiftContainer, that sets only the required fields, and verify
66
that the observed state corresponds to the spec.
77

88
Also validate that the OpenStack resource uses the name of the ORC object when
9-
`spec.resource.name` is not specified (SC-001).
9+
`spec.resource.name` is not specified.
1010

1111
## Step 01
1212

0 commit comments

Comments
 (0)