2c6f63a86f
The operator previously drove the Ceph manager dashboard REST API to manage RGW users, buckets and policies. That coupled it to a dashboard login, the dashboard's RGW wiring, and the dashboard's bucket API surface. Rebuild the Ceph integration to talk directly to radosgw the way the CLI does, using native Go libraries, while keeping every operator capability identical. The exported surface of internal/ceph is unchanged, so the three controllers and cmd/operator are untouched (bar the env-var/config plumbing already in flight for the radosgw move). - replace the internal/ceph client internals with github.com/ceph/go-ceph rgw/admin (Admin Ops API) for users, keys, quotas and bucket info/removal - add github.com/aws/aws-sdk-go-v2 S3 client for bucket create, versioning, policy, tagging and object lock, signed as the bucket owner - map go-ceph admin.ErrNoSuch*/ErrUserExists and smithy APIError codes into IsNotFound/IsConflict so controller create-vs-update branching is preserved - set S3 path-style addressing and WhenRequired checksum modes for RGW - delete the hand-rolled SigV4 signer, canonical-query and XML marshaling - keep policy.go/BuildBucketPolicy/BuildTagJSON as pure builders - replace the SigV4 signer tests with NewClient validation and error-classifier tests - keep CGO_ENABLED=0 distroless: only go-ceph's pure-Go rgw/admin is imported - rewrite README and docs/ceph-setup.md for the single RGW admin user (caps users=*;buckets=*) and CEPH_RGW_* credential Secret Claude-Session: https://claude.ai/code/session_016CEncETbf8cvy1PhsHfFHM
82 lines
2.6 KiB
Go
82 lines
2.6 KiB
Go
package ceph
|
|
|
|
import (
|
|
"testing"
|
|
|
|
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
smithy "github.com/aws/smithy-go"
|
|
"github.com/ceph/go-ceph/rgw/admin"
|
|
)
|
|
|
|
func TestNewClientValidation(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
cfg Config
|
|
wantErr bool
|
|
}{
|
|
{"ok", Config{Endpoint: "https://rgw:443", AccessKey: "a", SecretKey: "s"}, false},
|
|
{"no endpoint", Config{AccessKey: "a", SecretKey: "s"}, true},
|
|
{"no access key", Config{Endpoint: "https://rgw:443", SecretKey: "s"}, true},
|
|
{"no secret key", Config{Endpoint: "https://rgw:443", AccessKey: "a"}, true},
|
|
{"bad ca", Config{Endpoint: "https://rgw:443", AccessKey: "a", SecretKey: "s", CACert: []byte("not pem")}, true},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := NewClient(tc.cfg)
|
|
if (err != nil) != tc.wantErr {
|
|
t.Fatalf("NewClient err=%v wantErr=%v", err, tc.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsNotFound(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
err error
|
|
want bool
|
|
}{
|
|
{"nil", nil, false},
|
|
{"admin no such user", admin.ErrNoSuchUser, true},
|
|
{"admin no such bucket", admin.ErrNoSuchBucket, true},
|
|
{"admin no such key", admin.ErrNoSuchKey, true},
|
|
{"s3 no such bucket", &s3types.NoSuchBucket{}, true},
|
|
{"s3 no such key", &s3types.NoSuchKey{}, true},
|
|
{"generic no such bucket policy", &smithy.GenericAPIError{Code: "NoSuchBucketPolicy"}, true},
|
|
{"generic no such tag set", &smithy.GenericAPIError{Code: "NoSuchTagSet"}, true},
|
|
{"admin user exists is not notfound", admin.ErrUserExists, false},
|
|
{"unrelated", &smithy.GenericAPIError{Code: "AccessDenied"}, false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := IsNotFound(tc.err); got != tc.want {
|
|
t.Errorf("IsNotFound(%v)=%v want %v", tc.err, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsConflict(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
err error
|
|
want bool
|
|
}{
|
|
{"nil", nil, false},
|
|
{"admin user exists", admin.ErrUserExists, true},
|
|
{"admin bucket not empty", admin.ErrBucketNotEmpty, true},
|
|
{"s3 bucket already owned by you", &s3types.BucketAlreadyOwnedByYou{}, true},
|
|
{"s3 bucket already exists", &s3types.BucketAlreadyExists{}, true},
|
|
{"generic bucket already exists", &smithy.GenericAPIError{Code: "BucketAlreadyExists"}, true},
|
|
{"admin no such user is not conflict", admin.ErrNoSuchUser, false},
|
|
{"unrelated", &smithy.GenericAPIError{Code: "AccessDenied"}, false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := IsConflict(tc.err); got != tc.want {
|
|
t.Errorf("IsConflict(%v)=%v want %v", tc.err, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|