Skip to content

feat(0.22.0): config metadata CRUD + workspace orphan GC (FIIA P1-3, P1-4) - #204

Closed
ottomansky wants to merge 13 commits into
keboola:release/0.22.0from
ottomansky:fiia/pr8-config-metadata-workspace-gc
Closed

feat(0.22.0): config metadata CRUD + workspace orphan GC (FIIA P1-3, P1-4)#204
ottomansky wants to merge 13 commits into
keboola:release/0.22.0from
ottomansky:fiia/pr8-config-metadata-workspace-gc

Conversation

@ottomansky

Copy link
Copy Markdown
Contributor

Summary

  • Config metadata CRUD (config metadata-list / get-metadata / set-metadata / delete-metadata): full CRUD for arbitrary metadata key/value pairs on any configuration, using the branch-aware Storage API metadata endpoint. Branch auto-resolves from default when --branch is omitted.
  • Config set-folder (config set-folder --name NAME): sugar over set-metadata for KBC.configuration.folderName; organises configs into named folder groups visible in the Keboola UI.
  • Workspace orphan detection (workspace list --orphaned): lists keboola.sandboxes-backed workspaces whose sandbox config no longer exists.
  • Workspace GC (workspace gc [--dry-run] [--yes]): deletes all orphaned workspaces; --dry-run previews without touching anything; --yes skips interactive confirmation.

Changes

Layer Files
Client client.pylist_config_metadata, set_config_metadata, delete_config_metadata
Service config_service.py — 5 metadata methods + _resolve_metadata_branch_id helper
Service workspace_service.py_is_orphaned_workspace, list_workspaces(orphaned_only), gc_workspaces
Commands commands/config.py — 5 new commands; commands/workspace.py--orphaned flag + gc
Registry permissions.py — 6 new entries; hints/definitions/config.py + workspace.py
Context commands/context.py — updated agent reference docs
Plugin SKILL.md regenerated (+6 rows); plugin.json bumped to 0.22.0

Test plan

  • 62 new unit tests in tests/test_config_metadata.py and tests/test_workspace_gc.py — all passing
  • Standalone E2E classes TestE2EPR8ConfigMetadata (3 tests) and TestE2EPR8WorkspaceGC (1 test) validated against real Keboola stack
  • Workspace GC E2E: create → orphan (delete sandbox config) → list --orphaned → gc --dry-run → gc --yes → verify empty
  • Config metadata E2E: list (empty) → set → get → list (present) → delete → list (gone); set-folder; missing-key exits 1
  • ruff check + ruff format --check clean
  • Version bumped 0.21.10.22.0; make version-sync applied; SKILL.md regenerated

…P1-4)

Config metadata (P1-3):
- config metadata-list: list all metadata entries on a config (key-sorted)
- config get-metadata --key K: fetch a single value; exits 1 if absent
- config set-metadata --key K --value V: upsert a metadata entry
- config delete-metadata --metadata-id ID: delete by numeric ID (with confirmation)
- config set-folder --name NAME: sugar over KBC.configuration.folderName

All metadata commands auto-resolve the default branch via /dev-branches when
no --branch or active_branch_id is set (the Storage API metadata endpoint is
branch-aware-only, no non-branch route).

Workspace GC (P1-4):
- workspace list --orphaned: lists keboola.sandboxes workspaces whose sandbox
  config no longer exists
- workspace gc [--dry-run] [--yes]: deletes all orphaned workspaces; dry-run
  previews the list without touching anything; --yes bypasses confirmation

Tests: 62 new unit tests (test_config_metadata.py, test_workspace_gc.py) +
standalone E2E classes TestE2EPR8ConfigMetadata and TestE2EPR8WorkspaceGC
validated against a real Keboola stack.
…risk, add ConfigError tests

- _resolve_metadata_branch_id: catch bare Exception in addition to KeboolaApiError so
  network-level errors from list_dev_branches produce structured ConfigError output
- list/set/delete_config_metadata: move return dict inside try block to eliminate
  latent UnboundLocalError if _resolve_metadata_branch_id raises
- delete_workspace: initialise component=None before inner try (latent UnboundLocalError)
- workspace list command: add KeboolaApiError handler (consistent with all other workspace cmds)
- test_config_metadata: add ConfigError (exit 5) test for each of the 5 CLI commands
- test_workspace_gc: add dry-run-without-yes coverage test
- test_gc_no_confirmation_aborts: was a duplicate of test_gc_dry_run_json; rewritten
  to actually test the non-JSON abort path (user inputs 'n', exit 0, service not called)
- test_gc_delete_error_accumulated: >= 1 tightened to == 1 (one orphan → one error)
- test_gc_deletes_orphan_and_sandbox_config: add delete_config assertion (the test
  name claimed to verify sandbox-config cleanup but never asserted it)
- test_gc_delete_error_accumulated: replace misleading get_workspace side_effect with
  a normal return_value; the error being accumulated comes from delete_workspace 500,
  not from the swallowed get_workspace 404
- test_set_folder_empty_clears: assert response data['folder'] == '' (was missing)
…exception catch

- config_metadata_list: remove redundant 'from rich.markup import escape as _esc'
  inside loop; use module-level escape(); also escape the id field for consistency
- gc_workspaces: widen per-orphan except to bare Exception so network errors
  (ConnectionError, TimeoutError, etc.) are accumulated rather than crashing the loop
padak
padak previously requested changes Apr 22, 2026
@padak
padak dismissed their stale review April 22, 2026 21:41

Replacing with English version for broader team visibility.

@padak padak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes requested — 3 required fixes per CONTRIBUTING.md

Architecturally clean, tests complete (37 unit + 5 E2E), but three explicitly required artifacts are missing:

Required fixes

1. CLAUDE.md — the ## All CLI Commands section does not list the new commands (CONTRIBUTING.md line 202). Add:

kbagent config metadata-list --project NAME --component-id ID --config-id ID [--branch ID]
kbagent config get-metadata --project NAME --component-id ID --config-id ID --key KEY [--branch ID]
kbagent config set-metadata --project NAME --component-id ID --config-id ID --key KEY --value VALUE [--branch ID]
kbagent config delete-metadata --project NAME --component-id ID --config-id ID --metadata-id ID [--yes] [--branch ID]
kbagent config set-folder --project NAME --component-id ID --config-id ID --folder FOLDER [--branch ID]

kbagent workspace list [--project NAME] [--orphaned]
kbagent workspace gc [--project NAME] [--dry-run] [--yes]

(Verify signatures against your actual @typer.Option definitions.)

2. plugins/kbagent/skills/kbagent/references/commands-reference.md — same commands missing (CONTRIBUTING.md line 204: "this is a hand-maintained file, NOT auto-generated").

3. Rich escape in src/keboola_agent_cli/commands/workspace.py:491-502 — commit b4c5523 covered only config metadata; workspace gc was left unescaped:

# Lines ~491-502 — user-controlled fields in Rich markup:
f"([cyan]{ws.get('name', '')}[/cyan]) in '{ws['project_alias']}'"
f"[green]deleted[/green] workspace {ws['id']} in '{ws['project_alias']}'"
f"[red]error[/red] workspace {err.get('workspace_id', '?')}: {err.get('error', '')}"

Wrap ws['name'], ws['project_alias'], err['error'] with escape() from rich.markup. Workspace name comes from keboola.sandboxes config name (user-controlled), error from API response — potential Rich markup injection.

Nice-to-have (follow-up)

  • logger.exception() inside the gc_workspaces catch block (src/keboola_agent_cli/services/workspace_service.py:398) — today except Exception as exc: silently swallows programmer errors too (AttributeError etc.). Observability +1.
  • Consider a config-metadata-workflow.md reference — folder organization is a new agent-facing workflow pattern that deserves its own doc.
  • Unit test for the metadata-extraction enhancement in get_bucket_detail/get_table_detail (only E2E-covered today).

Positives

  • _resolve_metadata_branch_id (explicit → active → default) with actionable error messages is elegant.
  • Both test files (test_config_metadata.py 662 lines + test_workspace_gc.py 382 lines) are thorough.
  • FIIA P1-3 / P1-4 in the changelog entry, commit history shows a healthy review cycle (6 fix commits after the initial implementation).

Add the five config metadata CRUD commands (metadata-list, get-metadata,
set-metadata, delete-metadata, set-folder) and the new workspace list
--orphaned / workspace gc entries to the All CLI Commands section so
agents see the full inventory (CONTRIBUTING.md line 202).

Signatures verified against the actual @typer.Option definitions:
set-folder uses --name (not --folder) and workspace list/gc use a
repeatable --project; these differ from the review template and have
been mirrored to the real flags.
…ference

commands-reference.md is a hand-maintained file (CONTRIBUTING.md line 204)
that agents read via the kbagent skill. Add matching entries for:

- config metadata-list / get-metadata / set-metadata / delete-metadata
- config set-folder (with a pointer to config-metadata-workflow.md)
- workspace list --orphaned (updated the existing entry)
- workspace gc --dry-run / --yes (new entry)

Flag spelling follows the actual @typer.Option definitions; notably
set-folder takes --name (not --folder) and workspace list/gc accept a
repeatable --project.
…ields

Commit b4c5523 escaped user-controlled fields in config metadata output
but missed the workspace gc rendering block. Workspace name comes from
the keboola.sandboxes config name (user-controlled) and the error
string comes from the Keboola API response -- both reach Rich markup
rendering unescaped, allowing injection like "[red]bad[/red]" in a
workspace name to hijack formatting.

Wrap the three user-controlled interpolations with rich.markup.escape():
- ws.get("name", "") in the --dry-run branch
- ws["project_alias"] in both the --dry-run and delete branches
- err.get("error", "") in the error reporting branch

Static strings, numeric IDs, and literal markup tags are left alone.
…rvability

The per-orphan catch in gc_workspaces is a broad "except Exception" to
guarantee we never abort the sweep on an unexpected error. That's
correct for the user-facing flow (the error gets recorded and the next
orphan is processed), but it also silently swallows programmer errors
like AttributeError -- so bugs in the delete path would show up only as
a vague "error: <str>" line with no traceback.

Add logger.exception() inside the catch so the full traceback is
logged while leaving the user-facing flow unchanged (the error is still
accumulated into delete_errors). The module already has a logger at the
top.
Config metadata CRUD is a new agent-facing surface (introduced by PR keboola#204)
and folder organization via KBC.configuration.folderName is a pattern
that deserves its own doc rather than buried in the commands reference.

The workflow covers:
- overview and when to use metadata vs config update
- CLI cheatsheet for all five commands
- folder organization pattern using set-folder (with bulk example)
- full lifecycle example (list -> set -> get -> set-folder -> delete)
- --json response shapes for each command
- provider semantics (user vs system)
- branch awareness
@ottomansky

Copy link
Copy Markdown
Contributor Author

All three required fixes + two of three nice-to-haves:

  • docs(CLAUDE): list config metadata and workspace gc commands0feea1d.
  • docs(references): add config metadata and workspace gc to commands-reference6078889.
  • fix(workspace): escape Rich markup in gc output for user-controlled fieldsa4cc59a — wrapped ws.get('name'), ws['project_alias'] (both the dry-run and deleted lines), and err.get('error') with escape() from rich.markup. Numeric IDs left unescaped.
  • refactor(workspace): logger.exception on gc_workspaces catch for observability8e2dca4.
  • docs(references): add config-metadata-workflow.mde4ccf6c.

Two small signature deviations in the docs vs. your template — both reflect actual @typer.Option spellings:

  • config set-folder uses --name FOLDER (not --folder). Service arg is folder_name.
  • workspace list / workspace gc accept repeatable --project (list[str] | None) — documented as [--project NAME ...].

Re: unit tests for get_bucket_detail / get_table_detail metadata extraction — that feature lives on #205 (branch fiia/pr7-storage-descriptions), not this branch. On this branch get_bucket_detail is just bucket.get('description', '') and get_table_detail doesn't expose a description field at all. I added those tests on #205 instead, where the metadata-extraction code actually exists.

Targeted tests: 96/96 pass. make lint format-check skill-check version-check changelog-check all green.

@ottomansky
ottomansky requested a review from padak April 23, 2026 10:40
@padak
padak changed the base branch from main to release/0.22.0 April 23, 2026 11:56
padak added a commit that referenced this pull request Apr 23, 2026
New: config metadata-list/get/set/delete-metadata -- CRUD for arbitrary
key/value pairs on any configuration via branch-aware Storage API metadata
endpoint (FIIA P1-3).

New: config set-folder -- sugar over set-metadata for KBC.configuration.
folderName; organises configs into named folder groups.

New: workspace list --orphaned and workspace gc -- detect and clean up
sandbox-backed workspaces whose sandbox config no longer exists (FIIA P1-4).

Max's review fixes: CLAUDE.md + commands-reference docs, Rich markup escape
in GC output, logger.exception on gc catch, config-metadata-workflow.md.
@padak

padak commented Apr 23, 2026

Copy link
Copy Markdown
Member

Integrated into release/0.22.0 as squash commit 0f06607 (rebased locally on top of v0.21.2 to resolve merge conflicts with main; all Max's review fixes preserved). Original branch fiia/... unchanged.

Full integration PR coming as release/0.22.0 -> main.

@padak padak closed this Apr 23, 2026
padak added a commit that referenced this pull request Apr 23, 2026
- sync-workflow.md: new "Adopting an existing kbc Go CLI checkout" section for sync init --adopt-existing (#201).
- gotchas.md: new entries for exit code 7 (JOB_TIMEOUT_TERMINATED), --poll-strategy fixed, --log-tail-lines N, logTail response semantics (#202); new section for --deny-writes / --deny-destructive (#203); new section for sync init --adopt-existing (#201).
- workspace-workflow.md: new "Orphan detection + garbage collection" section for workspace list --orphaned + workspace gc (#204).
- commands-reference.md: sync init --adopt-existing flag documented.
- README.md: "What it does" table + "All commands" block updated with the 15 new commands shipped in 0.22.0 (flow CRUD, config metadata, workspace GC, storage describe, project pin + firewall, queue polling parity).
@padak padak mentioned this pull request Apr 23, 2026
6 tasks
@ottomansky
ottomansky deleted the fiia/pr8-config-metadata-workspace-gc branch May 11, 2026 15:15
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.

2 participants