rgw: controller for account management

PR only focuses on account creation, deletion and update.

Signed-off-by: Santosh <sapillai@redhat.com>
This commit is contained in:
Santosh committed 2026-03-13 11:21:46 +05:30
1 parent b0ae620426
commit c4ec7faee5
37 files changed
+2876 -75

No files matched your search

+5
View File
@@ -47,6 +47,11 @@ jobs:
check-latest: true
- name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
kube-api-lint:
name: kube-api-lint
@@ -21,6 +21,12 @@ concurrency:
permissions:
contents: read
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
jobs:
TestCephHelmSuite:
if: ${{ github.event_name == 'pull_request' && github.ref != 'refs/heads/master' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') }}
@@ -16,6 +16,12 @@ defaults:
permissions:
contents: read
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
# cancel the in-progress workflow when PR is refreshed.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
@@ -21,6 +21,12 @@ concurrency:
permissions:
contents: read
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
jobs:
TestCephMultiClusterDeploySuite:
if: ${{ github.event_name == 'pull_request' && github.ref != 'refs/heads/master' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') }}
@@ -21,6 +21,12 @@ concurrency:
permissions:
contents: read
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
jobs:
TestCephObjectSuite:
if: ${{ github.event_name == 'pull_request' && github.ref != 'refs/heads/master' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') }}
@@ -21,6 +21,12 @@ concurrency:
permissions:
contents: read
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
jobs:
TestCephSmokeSuite:
if: ${{ github.event_name == 'pull_request' && github.ref != 'refs/heads/master' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') }}
@@ -21,6 +21,12 @@ concurrency:
permissions:
contents: read
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
jobs:
TestCephUpgradeSuite:
if: ${{ github.event_name == 'pull_request' && github.ref != 'refs/heads/master' && !contains(github.event.pull_request.labels.*.name, 'skip-ci') }}
@@ -213,6 +213,11 @@ jobs:
TestCephObjectSuite:
runs-on: ubuntu-22.04
env:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
GOFLAGS: "-tags=ceph_preview"
strategy:
fail-fast: false
matrix:
+6
View File
@@ -1,5 +1,11 @@
---
version: "2"
run:
build-tags:
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
- ceph_preview
linters:
default: none
enable:
+4 -1
View File
@@ -26,6 +26,9 @@
"!Documentation/Helm-Charts",
],
"gopls": {
"formatting.gofumpt": true
"formatting.gofumpt": true,
"buildFlags": [
"-tags=ceph_preview"
]
}
}
+161
View File
@@ -4648,6 +4648,60 @@ Allow any string so that over-specified legacy clusters do not break on CRD upda
<td></td>
</tr></tbody>
</table>
<h3 id="ceph.rook.io/v1.CephObjectStoreAccount">CephObjectStoreAccount
</h3>
<div>
<p>CephObjectStoreAccount represent the RGW user account</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>metadata</code><br/>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#objectmeta-v1-meta">
Kubernetes meta/v1.ObjectMeta
</a>
</em>
</td>
<td>
Refer to the Kubernetes API documentation for the fields of the
<code>metadata</code> field.
</td>
</tr>
<tr>
<td>
<code>spec,omitzero</code><br/>
<em>
<a href="#ceph.rook.io/v1.ObjectStoreAccountSpec">
ObjectStoreAccountSpec
</a>
</em>
</td>
<td>
</td>
</tr>
<tr>
<td>
<code>status,omitzero</code><br/>
<em>
<a href="#ceph.rook.io/v1.ObjectStoreAccountStatus">
ObjectStoreAccountStatus
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.CephStatus">CephStatus
</h3>
<p>
@@ -11623,6 +11677,113 @@ If spec.sharedPools are also empty, then RGW pools (spec.dataPool and spec.metad
</p>
<div>
</div>
<h3 id="ceph.rook.io/v1.ObjectStoreAccountSpec">ObjectStoreAccountSpec
</h3>
<p>
(<em>Appears on:</em><a href="#ceph.rook.io/v1.CephObjectStoreAccount">CephObjectStoreAccount</a>)
</p>
<div>
<p>ObjectStoreAccountSpec represent the spec of a RGW Account</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>store</code><br/>
<em>
string
</em>
</td>
<td>
<p>Store is the CephObjectStore the account will be associated with</p>
</td>
</tr>
<tr>
<td>
<code>name</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Name is the desired display name of the RGW account if different from the CephObjectStoreAccount CR name.</p>
</td>
</tr>
<tr>
<td>
<code>accountID</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>AccountID uniquely identifies an account and resource ownership. Format should be RGW followed by 17 digits
(e.g., RGW00889737169837717). If not specified, the ID will be automatically generated.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.ObjectStoreAccountStatus">ObjectStoreAccountStatus
</h3>
<p>
(<em>Appears on:</em><a href="#ceph.rook.io/v1.CephObjectStoreAccount">CephObjectStoreAccount</a>)
</p>
<div>
<p>ObjectStoreAccountStatus represents the status of a CephObjectStoreAccount resource</p>
</div>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>phase</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>accountID</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>AccountID associated with the RGW user account</p>
</td>
</tr>
<tr>
<td>
<code>observedGeneration</code><br/>
<em>
int64
</em>
</td>
<td>
<em>(Optional)</em>
<p>ObservedGeneration is the latest generation observed by the controller.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="ceph.rook.io/v1.ObjectStoreHostingSpec">ObjectStoreHostingSpec
</h3>
<p>
+4 -1
View File
@@ -47,7 +47,10 @@ SHELL := /usr/bin/env bash
# Can be used or additional go build flags
BUILDFLAGS ?=
LDFLAGS ?=
TAGS ?=
# Required for go-ceph rgw/admin account APIs which are gated behind
# the ceph_preview build tag. Can be removed when go-ceph promotes
# the account API out of preview.
TAGS ?= ceph_preview
# turn on more verbose build
V ?= 0
@@ -149,6 +149,7 @@ rules:
- cephnvmeofgateways
- cephobjectstores
- cephobjectstoreusers
- cephobjectstoreaccounts
- cephobjectrealms
- cephobjectzonegroups
- cephobjectzones
@@ -176,6 +177,7 @@ rules:
- cephnvmeofgateways/status
- cephobjectstores/status
- cephobjectstoreusers/status
- cephobjectstoreaccounts/status
- cephobjectrealms/status
- cephobjectzonegroups/status
- cephobjectzones/status
@@ -200,6 +202,7 @@ rules:
- cephnvmeofgateways/finalizers
- cephobjectstores/finalizers
- cephobjectstoreusers/finalizers
- cephobjectstoreaccounts/finalizers
- cephobjectrealms/finalizers
- cephobjectzonegroups/finalizers
- cephobjectzones/finalizers
@@ -12565,6 +12565,106 @@ spec:
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
helm.sh/resource-policy: keep
name: cephobjectstoreaccounts.ceph.rook.io
spec:
group: ceph.rook.io
names:
kind: CephObjectStoreAccount
listKind: CephObjectStoreAccountList
plural: cephobjectstoreaccounts
singular: cephobjectstoreaccount
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1
schema:
openAPIV3Schema:
description: CephObjectStoreAccount represent the RGW user account
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: ObjectStoreAccountSpec represent the spec of a RGW Account
properties:
accountID:
description: |-
AccountID uniquely identifies an account and resource ownership. Format should be RGW followed by 17 digits
(e.g., RGW00889737169837717). If not specified, the ID will be automatically generated.
maxLength: 20
minLength: 20
pattern: ^RGW\d{17}$
type: string
x-kubernetes-validations:
- message: accountID is immutable
rule: self == oldSelf
name:
description: Name is the desired display name of the RGW account if different from the CephObjectStoreAccount CR name.
maxLength: 2048
minLength: 1
pattern: ^[a-zA-Z0-9 ._-]+$
type: string
store:
description: Store is the CephObjectStore the account will be associated with
maxLength: 253
minLength: 1
type: string
x-kubernetes-validations:
- message: store is immutable
rule: self == oldSelf
required:
- store
type: object
status:
description: ObjectStoreAccountStatus represents the status of a CephObjectStoreAccount resource
properties:
accountID:
description: AccountID associated with the RGW user account
maxLength: 20
minLength: 20
type: string
observedGeneration:
description: ObservedGeneration is the latest generation observed by the controller.
format: int64
type: integer
phase:
type: string
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
+3
View File
@@ -379,6 +379,7 @@ rules:
- cephnvmeofgateways
- cephobjectstores
- cephobjectstoreusers
- cephobjectstoreaccounts
- cephobjectrealms
- cephobjectzonegroups
- cephobjectzones
@@ -406,6 +407,7 @@ rules:
- cephnvmeofgateways/status
- cephobjectstores/status
- cephobjectstoreusers/status
- cephobjectstoreaccounts/status
- cephobjectrealms/status
- cephobjectzonegroups/status
- cephobjectzones/status
@@ -430,6 +432,7 @@ rules:
- cephnvmeofgateways/finalizers
- cephobjectstores/finalizers
- cephobjectstoreusers/finalizers
- cephobjectstoreaccounts/finalizers
- cephobjectrealms/finalizers
- cephobjectzonegroups/finalizers
- cephobjectzones/finalizers
+99
View File
@@ -12556,6 +12556,105 @@ spec:
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
name: cephobjectstoreaccounts.ceph.rook.io
spec:
group: ceph.rook.io
names:
kind: CephObjectStoreAccount
listKind: CephObjectStoreAccountList
plural: cephobjectstoreaccounts
singular: cephobjectstoreaccount
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1
schema:
openAPIV3Schema:
description: CephObjectStoreAccount represent the RGW user account
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: ObjectStoreAccountSpec represent the spec of a RGW Account
properties:
accountID:
description: |-
AccountID uniquely identifies an account and resource ownership. Format should be RGW followed by 17 digits
(e.g., RGW00889737169837717). If not specified, the ID will be automatically generated.
maxLength: 20
minLength: 20
pattern: ^RGW\d{17}$
type: string
x-kubernetes-validations:
- message: accountID is immutable
rule: self == oldSelf
name:
description: Name is the desired display name of the RGW account if different from the CephObjectStoreAccount CR name.
maxLength: 2048
minLength: 1
pattern: ^[a-zA-Z0-9 ._-]+$
type: string
store:
description: Store is the CephObjectStore the account will be associated with
maxLength: 253
minLength: 1
type: string
x-kubernetes-validations:
- message: store is immutable
rule: self == oldSelf
required:
- store
type: object
status:
description: ObjectStoreAccountStatus represents the status of a CephObjectStoreAccount resource
properties:
accountID:
description: AccountID associated with the RGW user account
maxLength: 20
minLength: 20
type: string
observedGeneration:
description: ObservedGeneration is the latest generation observed by the controller.
format: int64
type: integer
phase:
type: string
type: object
required:
- metadata
- spec
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.19.0
+18
View File
@@ -0,0 +1,18 @@
#################################################################################################################
# Create an object store account to manage RGW resources.
# kubectl create -f object-account.yaml
#################################################################################################################
apiVersion: ceph.rook.io/v1
kind: CephObjectStoreAccount
metadata:
name: my-account
namespace: rook-ceph # namespace:cluster
spec:
# The object store in which the account will be created
store: my-store
# An optional name for the RGW account if different from the CephObjectStoreAccount CR name
# name: "my-rgw-account"
# An optional unique account ID. Format: RGW followed by 17 digits (e.g., RGW00889737169837717).
# If not specified, Ceph will auto-generate the account ID.
# accountID: "RGW00889737169837717"
+2
View File
@@ -71,6 +71,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&CephObjectStoreList{},
&CephObjectStoreUser{},
&CephObjectStoreUserList{},
&CephObjectStoreAccount{},
&CephObjectStoreAccountList{},
&CephObjectRealm{},
&CephObjectRealmList{},
&CephObjectZoneGroup{},
+63
View File
@@ -2731,6 +2731,69 @@ type RGWServiceSpec struct {
Annotations Annotations `json:"annotations,omitempty"`
}
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
// CephObjectStoreAccount represent the RGW user account
type CephObjectStoreAccount struct {
metav1.TypeMeta `json:",inline"`
// +required
metav1.ObjectMeta `json:"metadata"`
// +required
Spec ObjectStoreAccountSpec `json:"spec,omitzero"`
// +optional
Status *ObjectStoreAccountStatus `json:"status,omitzero"` //nolint:kubeapilinter // MinProperties cannot be applied to a struct pointer field
}
// ObjectStoreAccountSpec represent the spec of a RGW Account
type ObjectStoreAccountSpec struct {
// Store is the CephObjectStore the account will be associated with
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:XValidation:message="store is immutable",rule="self == oldSelf"
Store string `json:"store,omitempty"`
// Name is the desired display name of the RGW account if different from the CephObjectStoreAccount CR name.
// +optional
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=2048
// +kubebuilder:validation:Pattern=`^[a-zA-Z0-9 ._-]+$`
Name string `json:"name,omitempty"`
// AccountID uniquely identifies an account and resource ownership. Format should be RGW followed by 17 digits
// (e.g., RGW00889737169837717). If not specified, the ID will be automatically generated.
// +optional
// +kubebuilder:validation:MinLength=20
// +kubebuilder:validation:MaxLength=20
// +kubebuilder:validation:Pattern=`^RGW\d{17}$`
// +kubebuilder:validation:XValidation:message="accountID is immutable",rule="self == oldSelf"
AccountID string `json:"accountID,omitempty"`
}
// ObjectStoreAccountStatus represents the status of a CephObjectStoreAccount resource
type ObjectStoreAccountStatus struct {
// +optional
Phase string `json:"phase,omitempty"` //nolint:kubeapilinter // Conditions are preferred over Phase
// +optional
// AccountID associated with the RGW user account
// +kubebuilder:validation:MinLength=20
// +kubebuilder:validation:MaxLength=20
AccountID string `json:"accountID,omitempty"`
// ObservedGeneration is the latest generation observed by the controller.
// +optional
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
}
// CephObjectStoreAccountList represents the Ceph object store accounts
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type CephObjectStoreAccountList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []CephObjectStoreAccount `json:"items"`
}
// +genclient
// +genclient:noStatus
// +kubebuilder:resource:shortName=nfs,path=cephnfses
@@ -1621,6 +1621,71 @@ func (in *CephObjectStore) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CephObjectStoreAccount) DeepCopyInto(out *CephObjectStoreAccount) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
if in.Status != nil {
in, out := &in.Status, &out.Status
*out = new(ObjectStoreAccountStatus)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephObjectStoreAccount.
func (in *CephObjectStoreAccount) DeepCopy() *CephObjectStoreAccount {
if in == nil {
return nil
}
out := new(CephObjectStoreAccount)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CephObjectStoreAccount) 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 *CephObjectStoreAccountList) DeepCopyInto(out *CephObjectStoreAccountList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CephObjectStoreAccount, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CephObjectStoreAccountList.
func (in *CephObjectStoreAccountList) DeepCopy() *CephObjectStoreAccountList {
if in == nil {
return nil
}
out := new(CephObjectStoreAccountList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CephObjectStoreAccountList) 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 *CephObjectStoreList) DeepCopyInto(out *CephObjectStoreList) {
*out = *in
@@ -4252,6 +4317,43 @@ func (in *ObjectSharedPoolsSpec) DeepCopy() *ObjectSharedPoolsSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectStoreAccountSpec) DeepCopyInto(out *ObjectStoreAccountSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreAccountSpec.
func (in *ObjectStoreAccountSpec) DeepCopy() *ObjectStoreAccountSpec {
if in == nil {
return nil
}
out := new(ObjectStoreAccountSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectStoreAccountStatus) DeepCopyInto(out *ObjectStoreAccountStatus) {
*out = *in
if in.ObservedGeneration != nil {
in, out := &in.ObservedGeneration, &out.ObservedGeneration
*out = new(int64)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreAccountStatus.
func (in *ObjectStoreAccountStatus) DeepCopy() *ObjectStoreAccountStatus {
if in == nil {
return nil
}
out := new(ObjectStoreAccountStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectStoreHostingSpec) DeepCopyInto(out *ObjectStoreHostingSpec) {
*out = *in
@@ -42,6 +42,7 @@ type CephV1Interface interface {
CephNVMeOFGatewaysGetter
CephObjectRealmsGetter
CephObjectStoresGetter
CephObjectStoreAccountsGetter
CephObjectStoreUsersGetter
CephObjectZonesGetter
CephObjectZoneGroupsGetter
@@ -109,6 +110,10 @@ func (c *CephV1Client) CephObjectStores(namespace string) CephObjectStoreInterfa
return newCephObjectStores(c, namespace)
}
func (c *CephV1Client) CephObjectStoreAccounts(namespace string) CephObjectStoreAccountInterface {
return newCephObjectStoreAccounts(c, namespace)
}
func (c *CephV1Client) CephObjectStoreUsers(namespace string) CephObjectStoreUserInterface {
return newCephObjectStoreUsers(c, namespace)
}
@@ -0,0 +1,68 @@
/*
Copyright 2018 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 v1
import (
context "context"
cephrookiov1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
scheme "github.com/rook/rook/pkg/client/clientset/versioned/scheme"
metav1 "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"
)
// CephObjectStoreAccountsGetter has a method to return a CephObjectStoreAccountInterface.
// A group's client should implement this interface.
type CephObjectStoreAccountsGetter interface {
CephObjectStoreAccounts(namespace string) CephObjectStoreAccountInterface
}
// CephObjectStoreAccountInterface has methods to work with CephObjectStoreAccount resources.
type CephObjectStoreAccountInterface interface {
Create(ctx context.Context, cephObjectStoreAccount *cephrookiov1.CephObjectStoreAccount, opts metav1.CreateOptions) (*cephrookiov1.CephObjectStoreAccount, error)
Update(ctx context.Context, cephObjectStoreAccount *cephrookiov1.CephObjectStoreAccount, opts metav1.UpdateOptions) (*cephrookiov1.CephObjectStoreAccount, error)
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
Get(ctx context.Context, name string, opts metav1.GetOptions) (*cephrookiov1.CephObjectStoreAccount, error)
List(ctx context.Context, opts metav1.ListOptions) (*cephrookiov1.CephObjectStoreAccountList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *cephrookiov1.CephObjectStoreAccount, err error)
CephObjectStoreAccountExpansion
}
// cephObjectStoreAccounts implements CephObjectStoreAccountInterface
type cephObjectStoreAccounts struct {
*gentype.ClientWithList[*cephrookiov1.CephObjectStoreAccount, *cephrookiov1.CephObjectStoreAccountList]
}
// newCephObjectStoreAccounts returns a CephObjectStoreAccounts
func newCephObjectStoreAccounts(c *CephV1Client, namespace string) *cephObjectStoreAccounts {
return &cephObjectStoreAccounts{
gentype.NewClientWithList[*cephrookiov1.CephObjectStoreAccount, *cephrookiov1.CephObjectStoreAccountList](
"cephobjectstoreaccounts",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *cephrookiov1.CephObjectStoreAccount { return &cephrookiov1.CephObjectStoreAccount{} },
func() *cephrookiov1.CephObjectStoreAccountList { return &cephrookiov1.CephObjectStoreAccountList{} },
),
}
}
@@ -84,6 +84,10 @@ func (c *FakeCephV1) CephObjectStores(namespace string) v1.CephObjectStoreInterf
return newFakeCephObjectStores(c, namespace)
}
func (c *FakeCephV1) CephObjectStoreAccounts(namespace string) v1.CephObjectStoreAccountInterface {
return newFakeCephObjectStoreAccounts(c, namespace)
}
func (c *FakeCephV1) CephObjectStoreUsers(namespace string) v1.CephObjectStoreUserInterface {
return newFakeCephObjectStoreUsers(c, namespace)
}
@@ -0,0 +1,52 @@
/*
Copyright 2018 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 (
v1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
cephrookiov1 "github.com/rook/rook/pkg/client/clientset/versioned/typed/ceph.rook.io/v1"
gentype "k8s.io/client-go/gentype"
)
// fakeCephObjectStoreAccounts implements CephObjectStoreAccountInterface
type fakeCephObjectStoreAccounts struct {
*gentype.FakeClientWithList[*v1.CephObjectStoreAccount, *v1.CephObjectStoreAccountList]
Fake *FakeCephV1
}
func newFakeCephObjectStoreAccounts(fake *FakeCephV1, namespace string) cephrookiov1.CephObjectStoreAccountInterface {
return &fakeCephObjectStoreAccounts{
gentype.NewFakeClientWithList[*v1.CephObjectStoreAccount, *v1.CephObjectStoreAccountList](
fake.Fake,
namespace,
v1.SchemeGroupVersion.WithResource("cephobjectstoreaccounts"),
v1.SchemeGroupVersion.WithKind("CephObjectStoreAccount"),
func() *v1.CephObjectStoreAccount { return &v1.CephObjectStoreAccount{} },
func() *v1.CephObjectStoreAccountList { return &v1.CephObjectStoreAccountList{} },
func(dst, src *v1.CephObjectStoreAccountList) { dst.ListMeta = src.ListMeta },
func(list *v1.CephObjectStoreAccountList) []*v1.CephObjectStoreAccount {
return gentype.ToPointerSlice(list.Items)
},
func(list *v1.CephObjectStoreAccountList, items []*v1.CephObjectStoreAccount) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
}
@@ -46,6 +46,8 @@ type CephObjectRealmExpansion interface{}
type CephObjectStoreExpansion interface{}
type CephObjectStoreAccountExpansion interface{}
type CephObjectStoreUserExpansion interface{}
type CephObjectZoneExpansion interface{}
@@ -0,0 +1,102 @@
/*
Copyright 2018 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 v1
import (
context "context"
time "time"
apiscephrookiov1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
versioned "github.com/rook/rook/pkg/client/clientset/versioned"
internalinterfaces "github.com/rook/rook/pkg/client/informers/externalversions/internalinterfaces"
cephrookiov1 "github.com/rook/rook/pkg/client/listers/ceph.rook.io/v1"
metav1 "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"
)
// CephObjectStoreAccountInformer provides access to a shared informer and lister for
// CephObjectStoreAccounts.
type CephObjectStoreAccountInformer interface {
Informer() cache.SharedIndexInformer
Lister() cephrookiov1.CephObjectStoreAccountLister
}
type cephObjectStoreAccountInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewCephObjectStoreAccountInformer constructs a new informer for CephObjectStoreAccount 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 NewCephObjectStoreAccountInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredCephObjectStoreAccountInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredCephObjectStoreAccountInformer constructs a new informer for CephObjectStoreAccount 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 NewFilteredCephObjectStoreAccountInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CephV1().CephObjectStoreAccounts(namespace).List(context.Background(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CephV1().CephObjectStoreAccounts(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CephV1().CephObjectStoreAccounts(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CephV1().CephObjectStoreAccounts(namespace).Watch(ctx, options)
},
},
&apiscephrookiov1.CephObjectStoreAccount{},
resyncPeriod,
indexers,
)
}
func (f *cephObjectStoreAccountInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredCephObjectStoreAccountInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *cephObjectStoreAccountInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&apiscephrookiov1.CephObjectStoreAccount{}, f.defaultInformer)
}
func (f *cephObjectStoreAccountInformer) Lister() cephrookiov1.CephObjectStoreAccountLister {
return cephrookiov1.NewCephObjectStoreAccountLister(f.Informer().GetIndexer())
}
@@ -52,6 +52,8 @@ type Interface interface {
CephObjectRealms() CephObjectRealmInformer
// CephObjectStores returns a CephObjectStoreInformer.
CephObjectStores() CephObjectStoreInformer
// CephObjectStoreAccounts returns a CephObjectStoreAccountInformer.
CephObjectStoreAccounts() CephObjectStoreAccountInformer
// CephObjectStoreUsers returns a CephObjectStoreUserInformer.
CephObjectStoreUsers() CephObjectStoreUserInformer
// CephObjectZones returns a CephObjectZoneInformer.
@@ -143,6 +145,11 @@ func (v *version) CephObjectStores() CephObjectStoreInformer {
return &cephObjectStoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// CephObjectStoreAccounts returns a CephObjectStoreAccountInformer.
func (v *version) CephObjectStoreAccounts() CephObjectStoreAccountInformer {
return &cephObjectStoreAccountInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// CephObjectStoreUsers returns a CephObjectStoreUserInformer.
func (v *version) CephObjectStoreUsers() CephObjectStoreUserInformer {
return &cephObjectStoreUserInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
@@ -81,6 +81,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Ceph().V1().CephObjectRealms().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("cephobjectstores"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ceph().V1().CephObjectStores().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("cephobjectstoreaccounts"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ceph().V1().CephObjectStoreAccounts().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("cephobjectstoreusers"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ceph().V1().CephObjectStoreUsers().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("cephobjectzones"):
@@ -0,0 +1,70 @@
/*
Copyright 2018 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 v1
import (
cephrookiov1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
labels "k8s.io/apimachinery/pkg/labels"
listers "k8s.io/client-go/listers"
cache "k8s.io/client-go/tools/cache"
)
// CephObjectStoreAccountLister helps list CephObjectStoreAccounts.
// All objects returned here must be treated as read-only.
type CephObjectStoreAccountLister interface {
// List lists all CephObjectStoreAccounts in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*cephrookiov1.CephObjectStoreAccount, err error)
// CephObjectStoreAccounts returns an object that can list and get CephObjectStoreAccounts.
CephObjectStoreAccounts(namespace string) CephObjectStoreAccountNamespaceLister
CephObjectStoreAccountListerExpansion
}
// cephObjectStoreAccountLister implements the CephObjectStoreAccountLister interface.
type cephObjectStoreAccountLister struct {
listers.ResourceIndexer[*cephrookiov1.CephObjectStoreAccount]
}
// NewCephObjectStoreAccountLister returns a new CephObjectStoreAccountLister.
func NewCephObjectStoreAccountLister(indexer cache.Indexer) CephObjectStoreAccountLister {
return &cephObjectStoreAccountLister{listers.New[*cephrookiov1.CephObjectStoreAccount](indexer, cephrookiov1.Resource("cephobjectstoreaccount"))}
}
// CephObjectStoreAccounts returns an object that can list and get CephObjectStoreAccounts.
func (s *cephObjectStoreAccountLister) CephObjectStoreAccounts(namespace string) CephObjectStoreAccountNamespaceLister {
return cephObjectStoreAccountNamespaceLister{listers.NewNamespaced[*cephrookiov1.CephObjectStoreAccount](s.ResourceIndexer, namespace)}
}
// CephObjectStoreAccountNamespaceLister helps list and get CephObjectStoreAccounts.
// All objects returned here must be treated as read-only.
type CephObjectStoreAccountNamespaceLister interface {
// List lists all CephObjectStoreAccounts in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*cephrookiov1.CephObjectStoreAccount, err error)
// Get retrieves the CephObjectStoreAccount from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*cephrookiov1.CephObjectStoreAccount, error)
CephObjectStoreAccountNamespaceListerExpansion
}
// cephObjectStoreAccountNamespaceLister implements the CephObjectStoreAccountNamespaceLister
// interface.
type cephObjectStoreAccountNamespaceLister struct {
listers.ResourceIndexer[*cephrookiov1.CephObjectStoreAccount]
}
@@ -130,6 +130,14 @@ type CephObjectStoreListerExpansion interface{}
// CephObjectStoreNamespaceLister.
type CephObjectStoreNamespaceListerExpansion interface{}
// CephObjectStoreAccountListerExpansion allows custom methods to be added to
// CephObjectStoreAccountLister.
type CephObjectStoreAccountListerExpansion interface{}
// CephObjectStoreAccountNamespaceListerExpansion allows custom methods to be added to
// CephObjectStoreAccountNamespaceLister.
type CephObjectStoreAccountNamespaceListerExpansion interface{}
// CephObjectStoreUserListerExpansion allows custom methods to be added to
// CephObjectStoreUserLister.
type CephObjectStoreUserListerExpansion interface{}
+2
View File
@@ -35,6 +35,7 @@ import (
"github.com/rook/rook/pkg/operator/ceph/nfs"
"github.com/rook/rook/pkg/operator/ceph/nvmeof"
"github.com/rook/rook/pkg/operator/ceph/object"
objectaccount "github.com/rook/rook/pkg/operator/ceph/object/account"
"github.com/rook/rook/pkg/operator/ceph/object/bucket"
"github.com/rook/rook/pkg/operator/ceph/object/cosi"
"github.com/rook/rook/pkg/operator/ceph/object/notification"
@@ -93,6 +94,7 @@ var AddToManagerFuncs = []func(manager.Manager, *clusterd.Context, context.Conte
subvolumegroup.Add,
radosnamespace.Add,
cosi.Add,
objectaccount.Add,
}
// AddToManagerOpFunc is a list of functions to add all Controllers to the Manager (entrypoint for
+81
View File
@@ -0,0 +1,81 @@
/*
Copyright 2026 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 object
import (
"context"
"github.com/ceph/go-ceph/rgw/admin"
"github.com/pkg/errors"
)
// GetAccount retrieves account information from RGW using the admin ops API.
func GetAccount(ctx context.Context, adminOpsContext *AdminOpsContext, accountID string) (admin.Account, error) {
if accountID == "" {
return admin.Account{}, errors.New("account ID cannot be empty")
}
account, err := adminOpsContext.AdminOpsClient.GetAccount(ctx, accountID)
if err != nil {
return admin.Account{}, errors.Wrapf(err, "failed to get account %q", accountID)
}
return account, nil
}
// CreateAccount creates a new RGW account using the admin ops API.
// If account.ID is empty, Ceph will auto-generate the account ID.
func CreateAccount(ctx context.Context, adminOpsContext *AdminOpsContext, account admin.Account) (admin.Account, error) {
if account.Name == "" {
return admin.Account{}, errors.New("account name cannot be empty")
}
createdAccount, err := adminOpsContext.AdminOpsClient.CreateAccount(ctx, account)
if err != nil {
return admin.Account{}, errors.Wrapf(err, "failed to create account %q", account.Name)
}
return createdAccount, nil
}
// ModifyAccount modifies an existing RGW account.
func ModifyAccount(ctx context.Context, adminOpsContext *AdminOpsContext, account admin.Account) (admin.Account, error) {
if account.ID == "" {
return admin.Account{}, errors.New("account ID cannot be empty")
}
modifiedAccount, err := adminOpsContext.AdminOpsClient.ModifyAccount(ctx, account)
if err != nil {
return admin.Account{}, errors.Wrapf(err, "failed to modify account %q", account.ID)
}
return modifiedAccount, nil
}
// DeleteAccount removes an RGW account using the admin ops API.
func DeleteAccount(ctx context.Context, adminOpsContext *AdminOpsContext, accountID string) error {
if accountID == "" {
return errors.New("account ID cannot be empty")
}
err := adminOpsContext.AdminOpsClient.DeleteAccount(ctx, accountID)
if err != nil {
return errors.Wrapf(err, "failed to delete account %q", accountID)
}
return nil
}
@@ -0,0 +1,492 @@
/*
Copyright 2026 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 account manages RGW accounts.
package account
import (
"context"
"fmt"
"reflect"
"strconv"
"github.com/ceph/go-ceph/rgw/admin"
"github.com/coreos/pkg/capnslog"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/events"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
"github.com/rook/rook/pkg/clusterd"
cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
"github.com/rook/rook/pkg/operator/ceph/object"
"github.com/rook/rook/pkg/operator/ceph/reporting"
"github.com/rook/rook/pkg/operator/k8sutil"
"github.com/rook/rook/pkg/util/log"
)
const (
controllerName = "ceph-object-store-account-controller"
)
// newMultisiteAdminOpsCtxFunc helps us mocking the admin ops API client in unit test
var newMultisiteAdminOpsCtxFunc = object.NewMultisiteAdminOpsContext
var logger = capnslog.NewPackageLogger("github.com/rook/rook", controllerName)
// Sets the type meta for the controller main object
var controllerTypeMeta = metav1.TypeMeta{
Kind: reflect.TypeFor[cephv1.CephObjectStoreAccount]().Name(),
APIVersion: fmt.Sprintf("%s/%s", cephv1.CustomResourceGroup, cephv1.Version),
}
// ReconcileObjectStoreAccount reconciles a CephObjectStoreAccount object
type ReconcileObjectStoreAccount struct {
client client.Client
scheme *runtime.Scheme
context *clusterd.Context
objContext *object.AdminOpsContext
cephClusterSpec *cephv1.ClusterSpec
clusterInfo *cephclient.ClusterInfo
opManagerContext context.Context
recorder events.EventRecorder
}
// Add creates a new CephObjectStoreAccount Controller and adds it to the Manager. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
// +kubebuilder:rbac:groups=ceph.rook.io,resources=cephobjectstoreaccounts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ceph.rook.io,resources=cephobjectstoreaccounts/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=ceph.rook.io,resources=cephobjectstoreaccounts/finalizers,verbs=update
func Add(mgr manager.Manager, context *clusterd.Context, opManagerContext context.Context, opConfig opcontroller.OperatorConfig) error {
return add(mgr, newReconciler(mgr, context, opManagerContext))
}
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerContext context.Context) reconcile.Reconciler {
return &ReconcileObjectStoreAccount{
client: mgr.GetClient(),
scheme: mgr.GetScheme(),
context: context,
opManagerContext: opManagerContext,
recorder: mgr.GetEventRecorder("rook-" + controllerName),
}
}
func add(mgr manager.Manager, r reconcile.Reconciler) error {
// Create a new controller
c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}
logger.Info("successfully started")
// Watch for changes on the CephObjectStoreAccount CRD object
err = c.Watch(
source.Kind(
mgr.GetCache(),
&cephv1.CephObjectStoreAccount{TypeMeta: controllerTypeMeta},
&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectStoreAccount]{},
opcontroller.WatchControllerPredicate[*cephv1.CephObjectStoreAccount](mgr.GetScheme()),
),
)
if err != nil {
return err
}
return nil
}
// Reconcile reads that state of the cluster for a CephObjectStoreAccount object and makes changes based on the state read
// and what is in the CephObjectStoreAccount.Spec
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
func (r *ReconcileObjectStoreAccount) Reconcile(context context.Context, request reconcile.Request) (reconcile.Result, error) {
defer opcontroller.RecoverAndLogException()
// workaround because the rook logging mechanism is not compatible with the controller-runtime logging interface
reconcileResponse, cephObjectStoreAccount, err := r.reconcile(request)
if err != nil {
r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.ReconcileFailedStatus)
log.NamedError(request.NamespacedName, logger, "failed to reconcile %v", err)
}
return reporting.ReportReconcileResult(logger, r.recorder, request, &cephObjectStoreAccount, reconcileResponse, err)
}
func (r *ReconcileObjectStoreAccount) reconcile(request reconcile.Request) (reconcile.Result, cephv1.CephObjectStoreAccount, error) {
// Fetch the CephObjectStoreAccount instance
cephObjectStoreAccount := &cephv1.CephObjectStoreAccount{}
err := r.client.Get(r.opManagerContext, request.NamespacedName, cephObjectStoreAccount)
if err != nil {
if kerrors.IsNotFound(err) {
log.NamedDebug(request.NamespacedName, logger, "CephObjectStoreAccount resource not found. Ignoring since object must be deleted.")
return reconcile.Result{}, *cephObjectStoreAccount, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrap(err, "failed to get CephObjectStoreAccount")
}
// update observedGeneration local variable with current generation value,
// because generation can be changed before reconcile got completed
// CR status will be updated at end of reconcile, so to reflect the reconcile has finished
observedGeneration := cephObjectStoreAccount.ObjectMeta.Generation
// Set a finalizer so we can do cleanup before the object goes away
generationUpdated, err := opcontroller.AddFinalizerIfNotPresent(r.opManagerContext, r.client, cephObjectStoreAccount)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrap(err, "failed to add finalizer")
}
if generationUpdated {
log.NamedInfo(request.NamespacedName, logger, "reconciling the object store account after adding finalizer")
return reconcile.Result{}, *cephObjectStoreAccount, nil
}
// The CR was just created, initializing status fields
if cephObjectStoreAccount.Status == nil {
r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.EmptyStatus)
}
// Make sure a CephCluster is present otherwise do nothing
cephCluster, isReadyToReconcile, cephClusterExists, reconcileResponse := opcontroller.IsReadyToReconcile(r.opManagerContext, r.client, request.NamespacedName, controllerName)
if !isReadyToReconcile {
// This handles the case where the Ceph Cluster is gone and we want to delete that CR
// We skip the deleteAccount() function since everything is gone already
//
// Also, only remove the finalizer if the CephCluster is gone
// If not, we should wait for it to be ready
// This handles the case where the operator is not ready to accept Ceph command but the cluster exists
if !cephObjectStoreAccount.GetDeletionTimestamp().IsZero() && !cephClusterExists {
// Remove finalizer
err = opcontroller.RemoveFinalizer(r.opManagerContext, r.client, cephObjectStoreAccount)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrap(err, "failed to remove finalizer")
}
// Return and do not requeue. Successful deletion.
return reconcile.Result{}, *cephObjectStoreAccount, nil
}
return reconcileResponse, *cephObjectStoreAccount, nil
}
r.cephClusterSpec = &cephCluster.Spec
// Populate clusterInfo during each reconcile
r.clusterInfo, _, _, err = opcontroller.LoadClusterInfo(r.context, r.opManagerContext, request.NamespacedName.Namespace, r.cephClusterSpec)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrap(err, "failed to populate cluster info")
}
// Validate the object store has been initialized
opsCtx, _, err := object.InitializeObjectStoreContext(r.context, r.clusterInfo, r.client, r.opManagerContext, cephObjectStoreAccount.Spec.Store, newMultisiteAdminOpsCtxFunc)
if err != nil {
if !cephObjectStoreAccount.GetDeletionTimestamp().IsZero() {
// Remove finalizer
err = opcontroller.RemoveFinalizer(r.opManagerContext, r.client, cephObjectStoreAccount)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrap(err, "failed to remove finalizer")
}
// Return and do not requeue. Successful deletion.
return reconcile.Result{}, *cephObjectStoreAccount, nil
}
log.NamedDebug(request.NamespacedName, logger, "ObjectStore resource not ready, retrying in %q. %v",
opcontroller.WaitForRequeueIfCephClusterNotReady.RequeueAfter.String(), err)
return opcontroller.WaitForRequeueIfCephClusterNotReady, *cephObjectStoreAccount, err
}
r.objContext = opsCtx
// DELETE: the CR was deleted
if !cephObjectStoreAccount.GetDeletionTimestamp().IsZero() {
log.NamedDebug(request.NamespacedName, logger, "deleting object store account")
r.recorder.Eventf(cephObjectStoreAccount, nil, corev1.EventTypeNormal, string(cephv1.ReconcileStarted), string(cephv1.ReconcileStarted), "deleting CephObjectStoreAccount %q", cephObjectStoreAccount.Name)
err := r.deleteAccount(cephObjectStoreAccount)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrapf(err, "failed to delete ceph object store account %q", cephObjectStoreAccount.Name)
}
// Remove finalizer
err = opcontroller.RemoveFinalizer(r.opManagerContext, r.client, cephObjectStoreAccount)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrap(err, "failed to remove finalizer")
}
// Return and do not requeue. Successful deletion.
return reconcile.Result{}, *cephObjectStoreAccount, nil
}
// CR is not deleted, continue reconciling
// Reconcile the account
accountID, err := r.reconcileAccount(cephObjectStoreAccount)
if err != nil {
return reconcile.Result{}, *cephObjectStoreAccount, errors.Wrapf(err, "failed to reconcile account %q", cephObjectStoreAccount.Name)
}
// Update the status with the account ID
r.updateStatusWithAccountID(observedGeneration, request.NamespacedName, accountID)
return reconcile.Result{}, *cephObjectStoreAccount, nil
}
// getAccountName returns the effective account name from the CR spec,
// falling back to the CR name if spec.Name is not set.
func getAccountName(cephObjectStoreAccount *cephv1.CephObjectStoreAccount) string {
if cephObjectStoreAccount.Spec.Name != "" {
return cephObjectStoreAccount.Spec.Name
}
return cephObjectStoreAccount.Name
}
// getAccountID looks up an already-persisted account ID from the CR,
// checking spec and status in priority order. It never generates
// a new ID. Returns "" when no ID has been recorded yet.
func getAccountID(cephObjectStoreAccount *cephv1.CephObjectStoreAccount) string {
if cephObjectStoreAccount.Spec.AccountID != "" {
return cephObjectStoreAccount.Spec.AccountID
}
if cephObjectStoreAccount.Status != nil && cephObjectStoreAccount.Status.AccountID != "" {
return cephObjectStoreAccount.Status.AccountID
}
return ""
}
// getOrGenerateAccountID returns the existing account ID if one is persisted,
// otherwise derives a deterministic ID from the CR's UID. This is used during
// reconciliation to ensure the same ID is reused across retries, preventing
// orphaned accounts if the operator crashes after creating an account but
// before persisting the account ID to the CR status.
func getOrGenerateAccountID(cephObjectStoreAccount *cephv1.CephObjectStoreAccount) (string, error) {
if id := getAccountID(cephObjectStoreAccount); id != "" {
return id, nil
}
if cephObjectStoreAccount.UID != "" {
nsName := types.NamespacedName{Namespace: cephObjectStoreAccount.Namespace, Name: cephObjectStoreAccount.Name}
log.NamedInfo(nsName, logger, "generating RGW account ID from the CR's UID")
id, err := generateDeterministicAccountID(cephObjectStoreAccount.UID)
if err != nil {
return "", errors.Wrap(err, "failed to generate deterministic account ID")
}
return id, nil
}
return "", nil
}
// generateDeterministicAccountID derives a stable RGW account ID from the
// CR's Kubernetes UID. It extracts 14 hex digits from the UUID, converts
// them to a 17-digit decimal number, and prefixes it with "RGW". The same
// UID always produces the same account ID.
func generateDeterministicAccountID(uid types.UID) (string, error) {
uidStr := string(uid)
l := len(uidStr)
if l < 14 {
return "", fmt.Errorf("UID too short to generate account ID: %q", uidStr)
}
last12 := uidStr[l-12:]
first2 := uidStr[:2]
value, err := strconv.ParseUint(last12+first2, 16, 64)
if err != nil {
return "", fmt.Errorf("failed to parse UID hex digits: %w", err)
}
return fmt.Sprintf("RGW%017d", value), nil
}
func (r *ReconcileObjectStoreAccount) reconcileAccount(cephObjectStoreAccount *cephv1.CephObjectStoreAccount) (string, error) {
accountID, err := getOrGenerateAccountID(cephObjectStoreAccount)
if err != nil {
return "", errors.Wrap(err, "failed to get or generate account ID")
}
desiredAccount := admin.Account{
ID: accountID,
Name: getAccountName(cephObjectStoreAccount),
}
nsName := types.NamespacedName{Namespace: cephObjectStoreAccount.Namespace, Name: cephObjectStoreAccount.Name}
// Try to fetch the existing account
liveAccountExists := true
_, err = object.GetAccount(r.opManagerContext, r.objContext, desiredAccount.ID)
if err != nil {
if errors.Is(err, admin.ErrNoSuchKey) {
log.NamedInfo(nsName, logger, "account %q does not exist", desiredAccount.ID)
liveAccountExists = false
} else {
return "", errors.Wrapf(err, "failed to check if account exists")
}
}
// If Account exists then validate ownership and check if it needs to be updated
if liveAccountExists {
// Verify ownership before modifying an existing account. Ownership is confirmed if
// the status already records the account ID (from a previous reconcile or from
// the pre-creation bookmark persisted to status before account creation).
hasOwnershipProof := cephObjectStoreAccount.Status != nil && cephObjectStoreAccount.Status.AccountID == desiredAccount.ID
if !hasOwnershipProof {
return "", fmt.Errorf("account ID %q already exists in RGW but is not managed by this CR; refusing to adopt a foreign account", desiredAccount.ID)
}
log.NamedInfo(nsName, logger, "ensuring account %q is in sync", desiredAccount.ID)
updatedAccount, err := object.ModifyAccount(r.opManagerContext, r.objContext, desiredAccount)
if err != nil {
return "", errors.Wrapf(err, "failed to modify account %q", desiredAccount.ID)
}
log.NamedInfo(nsName, logger, "successfully modified account %q", desiredAccount.ID)
return updatedAccount.ID, nil
}
// Persist the account ID to status before creating the account. This serves as a
// creation bookmark so that if the operator crashes after creating the account but
// before the final status update, the next reconcile can identify the account as
// owned by this CR. The phase is not set to Ready here — that happens after
// successful creation.
if err := r.persistAccountIDToStatus(cephObjectStoreAccount, desiredAccount.ID); err != nil {
return "", errors.Wrapf(err, "failed to persist account ID to status before creating account %q", desiredAccount.Name)
}
// Account doesn't exist, create it
log.NamedInfo(nsName, logger, "creating account %q", desiredAccount.Name)
createdAccount, err := object.CreateAccount(r.opManagerContext, r.objContext, desiredAccount)
if err != nil {
if errors.Is(err, admin.ErrAccountAlreadyExists) {
return "", fmt.Errorf("failed to create account with ID %q and name %q: an account with this ID or name already exists in RGW; ensure no conflicting account exists", desiredAccount.ID, desiredAccount.Name)
}
return "", errors.Wrapf(err, "failed to create account %q", desiredAccount.Name)
}
log.NamedInfo(nsName, logger, "successfully created account %q with ID %q", desiredAccount.Name, createdAccount.ID)
return createdAccount.ID, nil
}
// persistAccountIDToStatus persists the account ID to the CR status before
// creating the account in the backend. This is a no-op if the status already
// contains the desired account ID. The phase is not modified here; it will be
// set to Ready after the account is successfully created.
func (r *ReconcileObjectStoreAccount) persistAccountIDToStatus(cephObjectStoreAccount *cephv1.CephObjectStoreAccount, accountID string) error {
if cephObjectStoreAccount.Status != nil && cephObjectStoreAccount.Status.AccountID == accountID {
return nil
}
// Re-fetch the latest version to avoid resourceVersion conflicts, since earlier
// reconcile steps (e.g. updateStatus for initial empty status) may have updated
// the object on the server, making our in-memory copy stale.
nsName := types.NamespacedName{Namespace: cephObjectStoreAccount.Namespace, Name: cephObjectStoreAccount.Name}
latest := &cephv1.CephObjectStoreAccount{}
if err := r.client.Get(r.opManagerContext, nsName, latest); err != nil {
return errors.Wrapf(err, "failed to get latest version of object %q", nsName)
}
if latest.Status == nil {
latest.Status = &cephv1.ObjectStoreAccountStatus{}
}
latest.Status.AccountID = accountID
if err := reporting.UpdateStatus(r.client, latest); err != nil {
return errors.Wrapf(err, "failed to update object %q status", nsName)
}
// Update the caller's copy so subsequent checks see the persisted account ID
cephObjectStoreAccount.Status = latest.Status
return nil
}
func (r *ReconcileObjectStoreAccount) deleteAccount(cephObjectStoreAccount *cephv1.CephObjectStoreAccount) error {
nsName := types.NamespacedName{Namespace: cephObjectStoreAccount.Namespace, Name: cephObjectStoreAccount.Name}
accountID := getAccountID(cephObjectStoreAccount)
if accountID == "" {
log.NamedInfo(nsName, logger, "no account ID found, skipping deletion")
return nil
}
// Only delete the account if we have ownership proof (status contains the account ID).
// If the account ID is only in spec but was never successfully reconciled,
// it means the controller never created/adopted this account, so we must
// not delete what could be a foreign account.
hasOwnershipProof := cephObjectStoreAccount.Status != nil && cephObjectStoreAccount.Status.AccountID == accountID
if !hasOwnershipProof {
log.NamedInfo(nsName, logger, "account %q was never successfully managed by this CR, skipping deletion to avoid removing a foreign account", accountID)
return nil
}
log.NamedInfo(nsName, logger, "deleting account %q", accountID)
err := object.DeleteAccount(r.opManagerContext, r.objContext, accountID)
if err != nil {
// If account doesn't exist, consider it successful (idempotent)
if errors.Is(err, admin.ErrNoSuchKey) {
log.NamedInfo(nsName, logger, "account %q not found, considering deletion successful", accountID)
return nil
}
return errors.Wrapf(err, "failed to delete account %q", accountID)
}
log.NamedInfo(nsName, logger, "successfully deleted account %q", accountID)
return nil
}
func (r *ReconcileObjectStoreAccount) updateStatus(observedGeneration int64, name types.NamespacedName, status string) {
account := &cephv1.CephObjectStoreAccount{}
if err := r.client.Get(r.opManagerContext, name, account); err != nil {
if kerrors.IsNotFound(err) {
log.NamedDebug(name, logger, "CephObjectStoreAccount resource not found. Ignoring since object must be deleted.")
return
}
log.NamedWarning(name, logger, "failed to retrieve object store account %q to update status. %v", name, err)
return
}
if account.Status == nil {
account.Status = &cephv1.ObjectStoreAccountStatus{}
}
account.Status.Phase = status
if observedGeneration != k8sutil.ObservedGenerationNotAvailable {
account.Status.ObservedGeneration = &observedGeneration
}
if err := reporting.UpdateStatus(r.client, account); err != nil {
log.NamedError(name, logger, "failed to set object store account %q status to %q. %v", name, status, err)
return
}
log.NamedDebug(name, logger, "object store account %q status updated to %q", name, status)
}
func (r *ReconcileObjectStoreAccount) updateStatusWithAccountID(observedGeneration int64, name types.NamespacedName, accountID string) {
account := &cephv1.CephObjectStoreAccount{}
if err := r.client.Get(r.opManagerContext, name, account); err != nil {
if kerrors.IsNotFound(err) {
log.NamedDebug(name, logger, "CephObjectStoreAccount resource not found. Ignoring since object must be deleted.")
return
}
log.NamedWarning(name, logger, "failed to retrieve object store account %q to update status. %v", name, err)
return
}
if account.Status == nil {
account.Status = &cephv1.ObjectStoreAccountStatus{}
}
account.Status.Phase = k8sutil.ReadyStatus
account.Status.AccountID = accountID
if observedGeneration != k8sutil.ObservedGenerationNotAvailable {
account.Status.ObservedGeneration = &observedGeneration
}
if err := reporting.UpdateStatus(r.client, account); err != nil {
log.NamedError(name, logger, "failed to update object store account %q status. %v", name, err)
return
}
log.NamedDebug(name, logger, "object store account %q status updated with account ID %q", name, accountID)
}
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -114,7 +114,7 @@ const (
RGWAdminOpsUserSecretName = "rgw-admin-ops-user"
rgwAdminOpsUserAccessKey = "accessKey"
rgwAdminOpsUserSecretKey = "secretKey"
rgwAdminOpsUserCaps = "buckets=*;users=*;usage=read;metadata=read;zone=read"
rgwAdminOpsUserCaps = "accounts=*;buckets=*;users=*;usage=read;metadata=read;zone=read"
)
var rgwAdminOpsUserDisplayName = "RGW Admin Ops User"
+69
View File
@@ -43,6 +43,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
validation "k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
@@ -1280,3 +1281,71 @@ func SetDefaultRealm(objContext *Context, realmName string) error {
log.NamedInfo(objContext.NsName(), logger, "successfully set realm %q as default", realmName+"/"+objContext.clusterInfo.Namespace)
return nil
}
// InitializeObjectStoreContext finds the CephObjectStore by storeName, verifies it
// is initialized (has running RGW pods or is external), creates a multisite context,
// and initializes the admin ops API client.
func InitializeObjectStoreContext(
clusterdContext *clusterd.Context,
clusterInfo *cephclient.ClusterInfo,
k8sClient client.Client,
opManagerContext context.Context,
storeName string,
newAdminOpsCtxFunc func(*Context, *cephv1.ObjectStoreSpec) (*AdminOpsContext, error),
) (*AdminOpsContext, *cephv1.CephObjectStore, error) {
store, err := getObjectStore(k8sClient, opManagerContext, storeName, clusterInfo.Namespace)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to get object store %q", storeName)
}
if !store.Spec.IsExternal() {
if err := checkRGWPodsRunning(k8sClient, opManagerContext, clusterInfo.Namespace, storeName); err != nil {
return nil, nil, errors.Wrapf(err, "failed to detect if object store %q is initialized", storeName)
}
}
objContext, err := NewMultisiteContext(clusterdContext, clusterInfo, store)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to create multisite context for object store %q", store.Name)
}
opsContext, err := newAdminOpsCtxFunc(objContext, &store.Spec)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to initialize rgw admin ops client api")
}
return opsContext, store, nil
}
func getObjectStore(k8sClient client.Client, ctx context.Context, storeName, namespace string) (*cephv1.CephObjectStore, error) {
store := &cephv1.CephObjectStore{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: storeName, Namespace: namespace}, store); err != nil {
return nil, errors.Wrapf(err, "could not find CephObjectStore %q", storeName)
}
return store, nil
}
func checkRGWPodsRunning(k8sClient client.Client, ctx context.Context, namespace, storeName string) error {
pods := &v1.PodList{}
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels(labelsForRgw(storeName)),
}
if err := k8sClient.List(ctx, pods, listOpts...); err != nil {
if kerrors.IsNotFound(err) {
return errors.Wrap(err, "no rgw pod could not be found")
}
return errors.Wrap(err, "failed to list rgw pods")
}
if len(pods.Items) > 0 {
return nil
}
return errors.New("no rgw pod found")
}
func labelsForRgw(name string) map[string]string {
return map[string]string{"rgw": name, k8sutil.AppAttr: AppName}
}
+2 -72
View File
@@ -493,14 +493,9 @@ func (r *ReconcileObjectStoreUser) createOrUpdateCephUser(u *cephv1.CephObjectSt
}
func (r *ReconcileObjectStoreUser) initializeObjectStoreContext(u *cephv1.CephObjectStoreUser) error {
err := r.objectStoreInitialized(u)
opsContext, store, err := object.InitializeObjectStoreContext(r.context, r.clusterInfo, r.client, r.opManagerContext, u.Spec.Store, newMultisiteAdminOpsCtxFunc)
if err != nil {
return errors.Wrapf(err, "failed to detect if object store %q is initialized", u.Spec.Store)
}
store, err := r.getObjectStore(u)
if err != nil {
return errors.Wrapf(err, "failed to get object store %q", u.Spec.Store)
return err
}
// Check if the object store allows users to be created in any namespace
@@ -518,16 +513,6 @@ func (r *ReconcileObjectStoreUser) initializeObjectStoreContext(u *cephv1.CephOb
return errors.Wrapf(err, "failed to get CephObjectStore %q advertise endpoint for object store user", u.Spec.Store)
}
r.advertiseEndpoint = advertiseEndpoint
objContext, err := object.NewMultisiteContext(r.context, r.clusterInfo, store)
if err != nil {
return errors.Wrapf(err, "Multisite failed to set on object context for object store user")
}
opsContext, err := newMultisiteAdminOpsCtxFunc(objContext, &store.Spec)
if err != nil {
return errors.Wrap(err, "failed to initialized rgw admin ops client api")
}
r.objContext = opsContext
return nil
@@ -705,36 +690,6 @@ func (r *ReconcileObjectStoreUser) reconcileCephUserSecret(cephObjectStoreUser *
return reconcile.Result{}, nil
}
func (r *ReconcileObjectStoreUser) objectStoreInitialized(cephObjectStoreUser *cephv1.CephObjectStoreUser) error {
nsName := opcontroller.NsName(cephObjectStoreUser.Namespace, cephObjectStoreUser.Name)
cephObjectStore, err := r.getObjectStore(cephObjectStoreUser)
if err != nil {
return err
}
log.NamedDebug(nsName, logger, "CephObjectStore exists")
// If the rgw is external just return
// since there are no pods running
if cephObjectStore.Spec.IsExternal() {
return nil
}
// There are no pods running when the cluster is external
// Unless you pass the admin key...
pods, err := r.getRgwPodList(cephObjectStoreUser)
if err != nil {
return err
}
// check if at least one pod is running
if len(pods.Items) > 0 {
log.NamedDebug(nsName, logger, "%d RGW pods found where object store user %q is created", len(pods.Items), cephObjectStoreUser.Name)
return nil
}
return errors.New("no rgw pod found")
}
func (r *ReconcileObjectStoreUser) getObjectStore(user *cephv1.CephObjectStoreUser) (*cephv1.CephObjectStore, error) {
storeName := user.Spec.Store
nsName := client.ObjectKeyFromObject(user)
@@ -759,27 +714,6 @@ func (r *ReconcileObjectStoreUser) getObjectStore(user *cephv1.CephObjectStoreUs
return nil, errors.Errorf("could not find CephObjectStore %q referenced by CephObjectStoreUser %q", storeName, nsName)
}
func (r *ReconcileObjectStoreUser) getRgwPodList(cephObjectStoreUser *cephv1.CephObjectStoreUser) (*corev1.PodList, error) {
pods := &corev1.PodList{}
// check if ObjectStore is initialized
// rook does this by starting the RGW pod(s)
listOpts := []client.ListOption{
client.InNamespace(clusterStoreNamespace(cephObjectStoreUser)),
client.MatchingLabels(labelsForRgw(cephObjectStoreUser.Spec.Store)),
}
err := r.client.List(r.opManagerContext, pods, listOpts...)
if err != nil {
if kerrors.IsNotFound(err) {
return pods, errors.Wrap(err, "no rgw pod could not be found")
}
return pods, errors.Wrap(err, "failed to list rgw pods")
}
return pods, nil
}
// Namespace where the object store and cluster are expected to be found
func clusterStoreNamespace(user *cephv1.CephObjectStoreUser) string {
if user.Spec.ClusterNamespace != "" {
@@ -818,10 +752,6 @@ func (r *ReconcileObjectStoreUser) validateUser(u *cephv1.CephObjectStoreUser) e
return nil
}
func labelsForRgw(name string) map[string]string {
return map[string]string{"rgw": name, k8sutil.AppAttr: appName}
}
// updateStatus updates an object with a given status
func (r *ReconcileObjectStoreUser) updateStatus(observedGeneration int64, name types.NamespacedName, status string) {
user := &cephv1.CephObjectStoreUser{}