Skip to content

Commit 719edb9

Browse files
committed
feat: log the equivalent rustfs-cli command for every RustFS API call
Each provider call emits an 'equivalent: rc ...' line under the rc_cli tracing target (credentials redacted; $ALIAS/$USER_ALIAS indicate which identity the alias must be configured with). Policy documents log at debug. Test harness now surfaces these lines.
1 parent 9565440 commit 719edb9

7 files changed

Lines changed: 60 additions & 7 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rustfs-operator"
3-
version = "0.6.0"
3+
version = "0.6.1"
44
edition = "2024"
55
rust-version = "1.92"
66
license = "MIT OR Apache-2.0"

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ build and attach a linux-amd64 binary.
100100

101101
- **Reconcile loop**: finalizer-based; drift is re-checked every 5 minutes,
102102
errors retry after 15s and are reported in `.status.message`.
103+
- **CLI-equivalent logging**: every RustFS API call logs the equivalent
104+
[rustfs-cli](https://github.com/rustfs/cli) command under the `rc_cli`
105+
target (e.g. `equivalent: rc admin user add $ALIAS spark ****`), with
106+
credentials redacted. `$ALIAS` is an `rc alias` with the admin
107+
credentials, `$USER_ALIAS` one with the acting user's credentials.
108+
Shown at the default log level; silence with `RUST_LOG=info,rc_cli=warn`
109+
or set `RUST_LOG=rc_cli=info` to see only these lines. Policy documents
110+
are logged at `debug`.
103111
- **User passwords** are only applied at user creation; RustFS cannot
104112
update them in place. Rotate credentials by rotating AccessKeys instead
105113
(delete/recreate the AccessKey CR).

charts/rustfs-operator-crds/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ keywords:
1111
- operator
1212
- crds
1313
# version and appVersion are overwritten from the git tag by the release workflow
14-
version: 0.6.0
15-
appVersion: "0.6.0"
14+
version: 0.6.1
15+
appVersion: "0.6.1"

charts/rustfs-operator/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ keywords:
1010
- s3
1111
- operator
1212
# version and appVersion are overwritten from the git tag by the release workflow
13-
version: 0.6.0
14-
appVersion: "0.6.0"
13+
version: 0.6.1
14+
appVersion: "0.6.1"

charts/rustfs-resources/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ keywords:
1010
- s3
1111
- operator
1212
# version and appVersion are overwritten from the git tag by the release workflow
13-
version: 0.6.0
14-
appVersion: "0.6.0"
13+
version: 0.6.1
14+
appVersion: "0.6.1"

src/provider.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ impl RustFsProvider {
116116
Ok(Self { s3, admin, info })
117117
}
118118

119+
/// Log the equivalent rustfs-cli invocation for an API call.
120+
/// `$ALIAS` stands for an `rc alias` configured with the admin
121+
/// credentials; `$USER_ALIAS` for one configured with the acting
122+
/// user's credentials. Filter with RUST_LOG=rc_cli=info.
123+
fn cli(cmd: &str) {
124+
tracing::info!(target: "rc_cli", "equivalent: rc {cmd}");
125+
}
126+
119127
/// Admin client authenticated as a regular user (for service-account
120128
/// operations, which RustFS scopes to the calling identity).
121129
fn client_as(&self, username: &str, password: &str) -> Result<AdminClient> {
@@ -154,57 +162,70 @@ fn optional<T>(res: rc_core::Result<T>) -> Result<Option<T>> {
154162
#[async_trait]
155163
impl RustFs for RustFsProvider {
156164
async fn bucket_exists(&self, bucket: &str) -> Result<bool> {
165+
Self::cli(&format!("stat $ALIAS/{bucket}"));
157166
Ok(self.s3.bucket_exists(bucket).await?)
158167
}
159168

160169
async fn create_bucket(&self, bucket: &str) -> Result<()> {
170+
Self::cli(&format!("mb $ALIAS/{bucket}"));
161171
Ok(self.s3.create_bucket(bucket).await?)
162172
}
163173

164174
async fn delete_bucket(&self, bucket: &str) -> Result<()> {
175+
Self::cli(&format!("rb $ALIAS/{bucket}"));
165176
match self.s3.delete_bucket(bucket).await {
166177
Ok(()) => Ok(()),
167178
Err(e) => absent(e),
168179
}
169180
}
170181

171182
async fn get_versioning(&self, bucket: &str) -> Result<Option<bool>> {
183+
Self::cli(&format!("version info $ALIAS/{bucket}"));
172184
Ok(self.s3.get_versioning(bucket).await?)
173185
}
174186

175187
async fn set_versioning(&self, bucket: &str, enabled: bool) -> Result<()> {
188+
let verb = if enabled { "enable" } else { "suspend" };
189+
Self::cli(&format!("version {verb} $ALIAS/{bucket}"));
176190
Ok(self.s3.set_versioning(bucket, enabled).await?)
177191
}
178192

179193
async fn get_bucket_quota(&self, bucket: &str) -> Result<Option<u64>> {
194+
Self::cli(&format!("quota info $ALIAS/{bucket}"));
180195
match optional(self.admin.get_bucket_quota(bucket).await)? {
181196
Some(q) => Ok(q.quota.filter(|q| *q > 0)),
182197
None => Ok(None),
183198
}
184199
}
185200

186201
async fn set_bucket_quota(&self, bucket: &str, quota: u64) -> Result<()> {
202+
Self::cli(&format!("quota set $ALIAS/{bucket} {quota}"));
187203
self.admin.set_bucket_quota(bucket, quota).await?;
188204
Ok(())
189205
}
190206

191207
async fn get_user(&self, access_key: &str) -> Result<Option<User>> {
208+
Self::cli(&format!("admin user info $ALIAS {access_key}"));
192209
optional(self.admin.get_user(access_key).await)
193210
}
194211

195212
async fn create_user(&self, access_key: &str, secret_key: &str) -> Result<()> {
213+
Self::cli(&format!("admin user add $ALIAS {access_key} ****"));
196214
self.admin.create_user(access_key, secret_key).await?;
197215
Ok(())
198216
}
199217

200218
async fn delete_user(&self, access_key: &str) -> Result<()> {
219+
Self::cli(&format!("admin user rm $ALIAS {access_key}"));
201220
match self.admin.delete_user(access_key).await {
202221
Ok(()) => Ok(()),
203222
Err(e) => absent(e),
204223
}
205224
}
206225

207226
async fn set_user_status(&self, access_key: &str, enabled: bool) -> Result<()> {
227+
let verb = if enabled { "enable" } else { "disable" };
228+
Self::cli(&format!("admin user {verb} $ALIAS {access_key}"));
208229
let status = if enabled {
209230
UserStatus::Enabled
210231
} else {
@@ -214,21 +235,29 @@ impl RustFs for RustFsProvider {
214235
}
215236

216237
async fn set_user_policies(&self, user: &str, policies: &[String]) -> Result<()> {
238+
Self::cli(&format!(
239+
"admin policy attach $ALIAS {} --user {user}",
240+
policies.join(",")
241+
));
217242
Ok(self
218243
.admin
219244
.attach_policy(policies, PolicyEntity::User, user)
220245
.await?)
221246
}
222247

223248
async fn get_policy(&self, name: &str) -> Result<Option<Policy>> {
249+
Self::cli(&format!("admin policy info $ALIAS {name}"));
224250
optional(self.admin.get_policy(name).await)
225251
}
226252

227253
async fn put_policy(&self, name: &str, document: &str) -> Result<()> {
254+
Self::cli(&format!("admin policy create $ALIAS {name} policy.json"));
255+
tracing::debug!(target: "rc_cli", "policy.json: {document}");
228256
Ok(self.admin.create_policy(name, document).await?)
229257
}
230258

231259
async fn delete_policy(&self, name: &str) -> Result<()> {
260+
Self::cli(&format!("admin policy rm $ALIAS {name}"));
232261
match self.admin.delete_policy(name).await {
233262
Ok(()) => Ok(()),
234263
Err(e) => absent(e),
@@ -245,6 +274,9 @@ impl RustFs for RustFsProvider {
245274
password: &str,
246275
access_key: &str,
247276
) -> Result<Option<ServiceAccount>> {
277+
Self::cli(&format!(
278+
"admin service-account info $USER_ALIAS {access_key} # $USER_ALIAS uses {username}'s credentials"
279+
));
248280
let client = self.client_as(username, password)?;
249281
optional(client.get_service_account(access_key).await)
250282
}
@@ -258,6 +290,14 @@ impl RustFs for RustFsProvider {
258290
description: Option<String>,
259291
policy: Option<String>,
260292
) -> Result<()> {
293+
Self::cli(&format!(
294+
"admin service-account create $USER_ALIAS {access_key} ****{} # $USER_ALIAS uses {username}'s credentials",
295+
if policy.is_some() {
296+
" --policy policy.json"
297+
} else {
298+
""
299+
}
300+
));
261301
let client = self.client_as(username, password)?;
262302
client
263303
.create_service_account(CreateServiceAccountRequest {
@@ -278,6 +318,7 @@ impl RustFs for RustFsProvider {
278318
password: &str,
279319
access_key: &str,
280320
) -> Result<()> {
321+
Self::cli(&format!("admin service-account rm $ALIAS {access_key}"));
281322
// Try as admin first (covers keys whose owner was already deleted),
282323
// fall back to the owning user.
283324
match self.admin.delete_service_account(access_key).await {

tests/common/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ pub const RUSTFS_IMAGE: (&str, &str) = (
1717
/// feature is enabled). Call once at the start of each test.
1818
pub fn setup_test_env() {
1919
rustfs_operator::install_crypto_provider();
20+
// Surface the operator's rc-CLI-equivalent log lines in test output.
21+
let _ = tracing_subscriber::fmt()
22+
.with_env_filter("rc_cli=info")
23+
.try_init();
2024
for var in [
2125
"HTTP_PROXY",
2226
"HTTPS_PROXY",

0 commit comments

Comments
 (0)