Skip to content

refactor(workload-upgrade): migrate manual upgrade flow to new upgrade-check / upgrade API #2309

@peppescg

Description

@peppescg

Context

The ToolHive CLI bump to v0.29.0 (PR #2305) ships new REST endpoints dedicated to workload upgrades:

  • GET /api/v1beta/workloads/upgrade-check — bulk drift check (filter by group, include stopped via all=true)
  • GET /api/v1beta/workloads/{name}/upgrade-check — per-workload drift check
  • POST /api/v1beta/workloads/{name}/upgrade — apply upgrade (optional { env, secrets } merge body)

These cover end-to-end what the renderer is doing today by hand.

Current state (what we do "a mano")

Drift detection (client-side):

  • useIsServerFromRegistry (renderer/src/features/mcp-servers/hooks/use-is-server-from-registry.ts) parses the running image tag, matches the workload image against the registry catalog, and flags drift when registryTag !== localTag.
  • The card surfaces an amber ArrowUpCircle button when isFromRegistry && drift (renderer/src/features/mcp-servers/components/card-mcp-server/index.tsx:64).

Apply (via the generic edit endpoint):

  • useUpdateVersion (renderer/src/features/mcp-servers/hooks/use-update-version.tsx) orchestrates two paths:
    1. Direct update when there is no env-var drift: confirm dialog → useMutationUpdateWorkloadPOST /workloads/{name}/edit with the new image.
    2. Edit & review when env-var drift is detected: opens the full edit dialog pre-populated with imageOverride, envVarsOverride, secretsOverride, then submits via the same edit mutation.
  • The env-var diff itself is computed client-side from the registry entry.

New API surface (after #2305 lands)

GET .../upgrade-check returns a full UpgradeCheckResult:

  • status: up-to-date | upgrade-available | not-registry-sourced | server-not-found | unknown
  • current_image, candidate_image, registry_server
  • env_var_drift.added / .removed (with name, description, default, required, secret) — same data we recompute today for the review dialog
  • config_drift.transport / config_drift.permission_profiledrift we currently ignore

POST .../upgrade accepts PkgApiV1UpgradeRequest:

  • env?: Record<string, string> — overrides/merge
  • secrets?: string[] — secret references in <name>,target=<env> form
  • Empty body = upgrade preserving existing configuration

Proposed migration (two independent steps)

Step 1 — replace client-side drift detection

  • Replace useIsServerFromRegistry consumers with getApiV1BetaWorkloadsUpgradeCheckOptions (bulk on list views) and the per-name variant where granularity is needed.
  • Delete the tag parsing / registry comparison logic in the renderer.
  • The card's "update available" indicator becomes `status === 'upgrade-available'`.

Step 2 — replace the apply mutation

  • In use-update-version.tsx, swap useMutationUpdateWorkload for postApiV1BetaWorkloadsByNameUpgradeMutation.
  • Direct upgrade path → POST .../upgrade with empty body.
  • Edit-&-review path → map the form values for newly added vars/secrets into { env, secrets } and call upgrade instead of edit.
  • Continue using the edit endpoint only for unrelated config changes (it stays the right tool for arbitrary edits).

Wins

  • Backend becomes the source of truth for drift; renderer stops parsing image tags.
  • We start surfacing config_drift (transport, permission profile) which is currently silently ignored.
  • Clean semantic separation: edit for arbitrary edits, upgrade for the registry-driven version bump.

Caveats / open questions

  • PkgApiV1UpgradeRequest does not accept cmd_arguments or arbitrary workload fields. If we want to allow tweaking those at upgrade time, we either keep using edit for that case or split it into two steps (upgrade then edit).
  • The bulk endpoint takes all and group query params — confirm the list view's current filter semantics still work with one call.
  • Decide UX for status === 'not-registry-sourced' | 'server-not-found' | 'unknown' (today these workloads are silently treated as "no drift").

Acceptance criteria

  • Step 1: list view and card use getApiV1BetaWorkloadsUpgradeCheck* instead of useIsServerFromRegistry; the hook is removed (or reduced to non-upgrade callers, if any).
  • Step 2: useUpdateVersion uses postApiV1BetaWorkloadsByNameUpgrade for both direct and edit-&-review paths.
  • config_drift (transport / permission profile) is surfaced in the review dialog.
  • Existing tests pass / are updated; new tests cover the upgrade-status branches (upgrade-available, up-to-date, not-registry-sourced, etc.).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    TaskenhancementNew feature or requestneeds-triageIssue needs initial triage by a maintainer

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions