Skip to content

feat: introduce catalog cli commands#241

Merged
bradhe merged 3 commits intodevelopfrom
feature/tow-1760-introduce-catalog-secrets-commands
Apr 7, 2026
Merged

feat: introduce catalog cli commands#241
bradhe merged 3 commits intodevelopfrom
feature/tow-1760-introduce-catalog-secrets-commands

Conversation

@konstantinoscs
Copy link
Copy Markdown
Contributor

@konstantinoscs konstantinoscs commented Apr 2, 2026

Adding catalog commands to the cli.

How they will look:

$tower catalogs list

⠋ Listing catalogs

Name             Type         Environment
-----------------------------------------
prod-data        Lakekeeper     production
test-events      kafka        default
legacy-archive   s3           default
$tower catalogs show prod-data -e production

Catalog:      prod-data
Type:         Lakekeeper
Environment:  production

Properties
=======================
Name          Preview
-----------------------
host          db.in...
port          5432
username      admin

Closes TOW-1760

Summary by CodeRabbit

Release Notes

  • New Features
    • Added catalogs command with subcommands to list and view catalog details
    • list subcommand displays catalogs with Name, Type, and Environment columns; supports filtering by environment and viewing all available options
    • show subcommand provides detailed catalog information including properties; supports JSON output format

Arg::new("environment")
.short('e')
.long("environment")
.default_value("default")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the right thing to do here. I guess that's what default is for?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty sure yeah, looks like we have it everywhere we have this argument

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 79571130-9c1e-480a-92c4-ae1376dc9cf8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/tow-1760-introduce-catalog-secrets-commands

Comment @coderabbitai help to get the list of available commands and usage tips.

@konstantinoscs konstantinoscs requested review from bradhe and sammuti April 2, 2026 15:52
@konstantinoscs
Copy link
Copy Markdown
Contributor Author

@coderabbitai review this 💩 !

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

@konstantinoscs Sure, I'll review this PR right away! 🔍

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread crates/tower-cmd/src/catalogs.rs Outdated
Comment on lines +61 to +63
"Name".bold().yellow().to_string(),
"Type".bold().yellow().to_string(),
"Environment".bold().yellow().to_string(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i know there's a couple other places doing this, but it really shouldn't be necessary, we already format headers yellow in the output::table

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai is that true?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the output module
fd 'output.rs' --type f

# Search for the table function definition
rg -n 'fn table' --type rust -A 30

Length of output: 2376


🏁 Script executed:

#!/bin/bash
# Check how other commands pass headers to output::table
rg -n 'bold\(\)\.yellow\(\)\|yellow\(\)\.bold\(\)\|\.yellow()\|headers.*vec' --type rust -B2 -A2 crates/tower-cmd/src/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check how other commands pass headers to output::table
rg -n 'bold\(\)' --type rust crates/tower-cmd/src/ -A1 -B1

echo "---"
# Also check how other commands build header vecs
rg -n '"Name"\|"Type"\|"Environment"\|headers = vec' --type rust crates/tower-cmd/src/

Length of output: 2950


@konstantinoscs — socksy's claim is partially correct. Here's what the code actually does:

In crates/tower-cmd/src/output.rs (Line 461), output::table applies .yellow() to headers internally:

.title(headers.iter().map(|h| h.yellow().to_string()));

So passing headers that are already .bold().yellow() means .yellow() is applied twice — which is redundant. However, output::table does not apply .bold(), so the bold styling only comes from the callers (catalogs.rs, secrets.rs).

This means:

  • If you pass plain strings → headers are yellow only (from output::table)
  • If you pass .bold().yellow() strings → headers are bold + yellow (yellow is double-applied but has no extra effect)

The cleanest fix would be one of these two options:

Option A — Strip .yellow() from callers (keep .bold()), since output::table adds it:

let headers = vec![
    "Name".bold().to_string(),
    "Type".bold().to_string(),
    "Environment".bold().to_string(),
];

Option B — Update output::table to apply .bold().yellow() so callers can pass plain strings:

// In output.rs
.title(headers.iter().map(|h| h.bold().yellow().to_string()));

Option B would also fix the same redundancy in secrets.rs at the source, which would be the more principled fix.

Either way, this is an existing pattern from secrets.rs that predates this PR, so the concern applies equally to both files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai and @socksy: I've consolidated all headers to use the same style. PTAL

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current state of headers in catalogs.rs, secrets.rs, and output.rs table function
echo "=== catalogs.rs headers ==="
rg -n 'bold\(\)|yellow\(\)|headers\s*=\s*vec' --type rust crates/tower-cmd/src/catalogs.rs -A1 -B1

echo "=== secrets.rs headers ==="
rg -n 'bold\(\)|yellow\(\)|headers\s*=\s*vec' --type rust crates/tower-cmd/src/secrets.rs -A1 -B1

echo "=== output.rs table title ==="
rg -n 'title\|bold\(\)|yellow\(\)' --type rust crates/tower-cmd/src/output.rs -A2 -B2

Length of output: 1774


@konstantinoscs — The fix looks great! Option B was applied correctly:

  • catalogs.rs and secrets.rs now pass plain strings (e.g., vec!["Name", "Type", "Environment"]) to output::table
  • output.rs (Line 461) centralizes the styling: .title(headers.iter().map(|h| h.bold().yellow().to_string()))

This is the cleaner, more principled approach — styling is defined in one place and callers don't need to worry about it. LGTM! ✅

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/tower-cmd/src/catalogs.rs (1)

79-121: Consider using with_spinner for consistency with do_list and sibling modules.

The do_show function manually calls the API and handles errors, while do_list uses output::with_spinner. Based on the patterns in teams.rs (line 59) and apps.rs (line 206), using with_spinner provides a consistent UX with a progress indicator during API calls.

♻️ Suggested refactor using with_spinner
 pub async fn do_show(config: Config, args: &ArgMatches) {
     let name = args
         .get_one::<String>("catalog_name")
         .expect("catalog_name is required");
     let env = cmd::get_string_flag(args, "environment");

-    match api::describe_catalog(&config, name, &env).await {
-        Ok(response) => {
-            if output::get_output_mode().is_json() {
-                output::json(&response);
-                return;
-            }
-
-            let catalog = &response.catalog;
-
-            output::detail("Catalog", &catalog.name);
-            output::detail("Type", &catalog.r#type);
-            output::detail("Environment", &catalog.environment);
-
-            if !catalog.properties.is_empty() {
-                output::newline();
-                output::header("Properties");
-
-                let headers = vec![
-                    "Name".bold().yellow().to_string(),
-                    "Preview".bold().yellow().to_string(),
-                ];
-                let data = catalog
-                    .properties
-                    .iter()
-                    .map(|prop| {
-                        vec![
-                            prop.name.clone(),
-                            prop.preview.dimmed().to_string(),
-                        ]
-                    })
-                    .collect();
-                output::table(headers, data, Some(&response));
-            }
-        }
-        Err(err) => output::tower_error_and_die(err, "Fetching catalog details failed"),
+    let response =
+        output::with_spinner("Fetching catalog details", api::describe_catalog(&config, name, &env)).await;
+
+    if output::get_output_mode().is_json() {
+        output::json(&response);
+        return;
     }
+
+    let catalog = &response.catalog;
+
+    output::detail("Catalog", &catalog.name);
+    output::detail("Type", &catalog.r#type);
+    output::detail("Environment", &catalog.environment);
+
+    if !catalog.properties.is_empty() {
+        output::newline();
+        output::header("Properties");
+
+        let headers = vec![
+            "Name".bold().yellow().to_string(),
+            "Preview".bold().yellow().to_string(),
+        ];
+        let data = catalog
+            .properties
+            .iter()
+            .map(|prop| {
+                vec![
+                    prop.name.clone(),
+                    prop.preview.dimmed().to_string(),
+                ]
+            })
+            .collect();
+        output::table(headers, data, Some(&response));
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/tower-cmd/src/catalogs.rs` around lines 79 - 121, Replace the manual
API call and match in do_show with output::with_spinner that executes
api::describe_catalog(&config, name, &env).await to keep parity with do_list and
sibling modules; move the Ok(response) success path (JSON branch, extracting
response.catalog, calling output::detail, output::newline/header/table) into the
spinner's success continuation, and ensure any error returned from with_spinner
is forwarded to output::tower_error_and_die(err, "Fetching catalog details
failed") so error handling remains identical. Use the same response variable
names and preserve building headers/data for output::table exactly as currently
implemented.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/tower-cmd/src/catalogs.rs`:
- Around line 79-121: Replace the manual API call and match in do_show with
output::with_spinner that executes api::describe_catalog(&config, name,
&env).await to keep parity with do_list and sibling modules; move the
Ok(response) success path (JSON branch, extracting response.catalog, calling
output::detail, output::newline/header/table) into the spinner's success
continuation, and ensure any error returned from with_spinner is forwarded to
output::tower_error_and_die(err, "Fetching catalog details failed") so error
handling remains identical. Use the same response variable names and preserve
building headers/data for output::table exactly as currently implemented.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7dad485d-31bb-4293-a421-181e90c3fe6a

📥 Commits

Reviewing files that changed from the base of the PR and between b0f0993 and 6084e0c.

📒 Files selected for processing (3)
  • crates/tower-cmd/src/api.rs
  • crates/tower-cmd/src/catalogs.rs
  • crates/tower-cmd/src/lib.rs

@konstantinoscs konstantinoscs force-pushed the feature/tow-1760-introduce-catalog-secrets-commands branch from 17376aa to 0de2e26 Compare April 6, 2026 23:08
@konstantinoscs konstantinoscs force-pushed the feature/tow-1760-introduce-catalog-secrets-commands branch from 0de2e26 to 6323464 Compare April 6, 2026 23:15
@bradhe bradhe merged commit a827c95 into develop Apr 7, 2026
29 checks passed
@bradhe bradhe deleted the feature/tow-1760-introduce-catalog-secrets-commands branch April 7, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants