Skip to content

feat: migrate flashduty-cli onto go-flashduty (covered+non-enriched, dual-client)#24

Merged
ysyneu merged 11 commits into
mainfrom
feat/migrate-go-flashduty
May 31, 2026
Merged

feat: migrate flashduty-cli onto go-flashduty (covered+non-enriched, dual-client)#24
ysyneu merged 11 commits into
mainfrom
feat/migrate-go-flashduty

Conversation

@ysyneu
Copy link
Copy Markdown
Contributor

@ysyneu ysyneu commented May 30, 2026

What

Begin migrating flashduty-cli off the hand-written flashduty-sdk onto the generated go-flashduty client. Behavior-preserving, dual-client transition: go.mod requires both SDKs; migrated handlers call *go-flashduty.Client (wired via RunContext.GFClient / newGFClientFn); every deferred handler keeps an in-code TODO(go-flashduty migration) naming exactly why it stays on legacy.

This is the CLI counterpart to flashduty-mcp-server#61 (same dual-client pattern, same go-flashduty v0.3.0).

Migrated (28 commands)

  • incident batch ops: ack, unack, close→Resolve, wake, merge, snooze, reopen, reassign→Assign, add-responder→ResponderAdd, comment, disable-merge, remove, create + war-room list/get/delete
  • alert merge, events · alert-event list
  • insight team, channel, responder, top-alerts
  • audit search · statuspage create-timeline · monit-query diagnose · monit-agent catalog, invoke

Deferred on legacy SDK (each TODO-annotated)

  • endpoint gap (pending upstream): war-room create / add-member / default-observers, change list/trend, statuspage list, insight notifications, mcp create
  • shape / enrichment divergence: incident list/get/timeline/feed/similar/postmortem, alert list/get/timeline, oncall who/schedule, statuspage changes/create-incident, incident update (/incident/reset drops --field), monit-query rows (raw-bytes→structured)

The migration map's "covered" means the endpoint exists — not that the request shape is compatible. Several map-"covered" commands are structurally divergent or enrichment-dependent and were deliberately deferred rather than force-fit.

Other changes

  • TOON moved from sdk.Marshal(.., TOON) to toon-go directly (promoted to a direct dep); the SDK stays a pure client.
  • Test seam: go-flashduty's client is a concrete type (not an interface), so migrated-command tests run against gfStub — an httptest server that records path + decoded body and replies with a canned envelope (internal/cli/gfstub_test.go).
  • Exact legacy wire on assignment: both incident create and incident reassign set assigned_to.type = "assign" explicitly. The hand-written SDK forced it; leaving it empty would let the backend relabel an already-assigned incident as reassign in the feed/IM cards. Guarded by two new wire-assertion tests. (Whether reassign should say "reassign" is a separate product decision, intentionally not bundled here.)

Verification

  • go build + go test (all 6 packages) + go vet + gofmt -l clean.
  • In-process e2e against api-dev passes for migrated commands with coverage. Remaining e2e failures reproduce identically on a clean origin/main baseline (stale --title flag tests, 31-day-window / 429 / 401-override-key env issues, and the not-migrated change list) — pre-existing, not introduced here.

Follow-up (blocked on upstream)

Once the gap endpoints are documented upstream → re-vendored into go-flashduty → released, switch the deferred handlers off legacy and drop the flashduty-sdk dep. Same convergence as the MCP PR.

ysyneu added 11 commits May 30, 2026 17:50
…lient)

Begin moving flashduty-cli off the hand-written flashduty-sdk onto the
generated go-flashduty client. This is a behavior-preserving, dual-client
transition: go.mod requires both SDKs, migrated handlers call the new
`*go-flashduty.Client` (wired via `RunContext.GFClient` / `newGFClientFn`),
and every deferred handler keeps an in-code `TODO(go-flashduty migration)`
naming exactly why it stays on the legacy client.

Migrated (28 commands): incident batch ops (ack/unack/close→Resolve/wake/
merge/snooze/reopen/reassign→Assign/add-responder/comment/disable-merge/
remove/create + war-room list/get/delete), alert merge/events, alert-event
list, insight team/channel/responder/top-alerts, audit search, statuspage
create-timeline, monit-query diagnose, monit-agent catalog/invoke.

Deferred on legacy SDK (each TODO-annotated):
- endpoint gap (pending upstream): war-room create/add-member/default-
  observers, change list/trend, statuspage list, insight notifications,
  mcp create
- shape/enrichment divergence: incident list/get/timeline/feed/similar/
  postmortem, alert list/get/timeline, oncall who/schedule, statuspage
  changes/create-incident, incident update (/reset drops --field),
  monit-query rows (raw→structured)

Other changes:
- TOON output moved from `sdk.Marshal(.., TOON)` to `toon-go` directly
  (toon promoted to a direct dep); SDK stays a pure client.
- New test seam: go-flashduty's client is a concrete type, not an
  interface, so migrated-command tests run against `gfStub`, an
  httptest server that records the path + decoded body and replies with
  a canned envelope (internal/cli/gfstub_test.go).
- Preserve exact legacy wire on assignment: both `incident create` and
  `incident reassign` set assigned_to.type = "assign" explicitly (the
  hand-written SDK forced it; leaving it empty would let the backend
  relabel an already-assigned incident as "reassign"). Guarded by two
  new wire-assertion tests.

Verified: go build + go test (all 6 packages) + go vet + gofmt clean;
in-process e2e against api-dev passes for migrated commands with coverage
(remaining e2e failures reproduce identically on a clean origin/main
baseline — stale --title flag tests, 31-day-window/429/401 env issues,
and the not-migrated `change list`).
Bump go-flashduty 0.3.0 -> 0.4.0, which now documents the endpoints these
previously-deferred commands needed, and move them off the hand-written
flashduty-sdk onto the typed ctx.GFClient.

Migrated (legacy SDK call -> go-flashduty method):
- change list            ListChanges                     -> Changes.List
- statuspage list        ListStatusPages                 -> StatusPages.ReadPageList (page-id filter applied client-side)
- incident war-room create        CreateIncidentWarRoom            -> Incidents.WarRoomCreate
- incident war-room create (auto) ListWarRoomEnabledDataSources    -> ImIntegrations.List
- incident war-room add-member    AddIncidentWarRoomMembers        -> Incidents.WriteAddWarRoomMember
- incident war-room default-observers GetIncidentWarRoomDefaultObservers -> Incidents.ReadGetWarRoomDefaultObservers
- mcp create             CreateMCPServer                 -> McpServers.WriteServerCreate

Output (columns, order, footers, TOON) preserved exactly. change list now
clamps non-positive --limit/--page to the legacy defaults (20/1) before
sending, since go-flashduty forwards values verbatim and the server rejects
< 1; the footer still shows the raw --page value, matching legacy behavior.
statuspage list STATUS column stays empty (the /status-page/list endpoint and
the legacy SDK never populated overall_status).

Kept on legacy (no clean go-flashduty equivalent):
- statuspage changes  -> hits /status-page/change/active/list; go-flashduty only
  has the general /status-page/change/list (different semantics, requires status).
- insight notifications -> go-flashduty Analytics has no notification-trend endpoint.
Both annotated with TODO(go-flashduty migration). The shape/enrichment-deferred
commands (incident list/get/timeline/feed/similar/postmortem/update, alert
list/get/timeline, oncall who/schedule, statuspage create-incident, insight
responder, monit-query rows) remain on legacy as before.

Dropped the now-unused war-room/change/statuspage-list/mcp methods from the
flashdutyClient interface (incl. 3 war-room methods left dead by the prior
migration) and the orphaned mockClient/mockIncidentWarRoom stubs. Migrated the
affected unit tests onto the gfStub httptest seam (added a path-aware payload
hook for war-room create's two-call auto-discover flow) and added gfStub-backed
tests for change list, statuspage list, and mcp create.

flashduty-sdk dependency retained for the commands still on legacy.
The notifications command's backing report API (/report/* QueryNotificationTrend)
is being retired, so it is intentionally excluded from go-flashduty's spec — there
is no SDK method and there won't be one. Reword the TODO from a pending-migration
note to an explicit do-not-migrate so it isn't re-pointed at the SDK later.
The notification-trend report API backing this command is being taken
offline, so the command is removed outright rather than kept on the legacy
SDK. Drops the cobra subcommand, its flashdutyClient.QueryNotificationTrend
interface method, and the mockClient stub. No other caller referenced it.
Move the last two read commands that were pinned to the hand-written SDK onto
go-flashduty:

- insight responder: now calls Analytics.ByResponder. Drops the EMAIL column —
  the backend /insight/responder (RspdIncMetrics) never returns an email, so the
  legacy SDK's ResponderInsightItem.Email was always blank (a decode-only field).
- incident feed: now calls Incidents.Feed. go-flashduty returns raw feed items,
  so resolveFeedOperators() replicates the legacy operator-name enrichment by
  resolving each entry's creator_id via Members.PersonInfos (best-effort; falls
  back to the numeric ID, or 'system' for creator_id 0).

Removes the now-dead GetIncidentFeed + QueryInsightByResponder from the
flashdutyClient interface and mockClient, and rewrites the feed-empty test to use
the gfStub httptest seam. Build/vet/gofmt/test green; both commands live-verified
against api-dev (responder lists real data sans EMAIL; feed resolves operator
names).
… go-flashduty

Drops the hand-written flashduty-sdk dependency entirely. Every command now
builds a concrete *go-flashduty.Client; the shared flashdutyClient interface,
mockClient, and the legacy newClient factory are removed.

- Migrate the remaining ~25 legacy commands (alert, channel, escalation-rule,
  field, incident, insight, member, monit-query, oncall, postmortem,
  status-page, status-page-migrate, team) onto typed go-flashduty services.
- whoami/login: resolve identity via Members.MemberInfo + Account.Info.
- template: vendor the client-side authoring metadata (channels, size limits,
  variable/function catalogs) into internal/cli/templatemeta.go -- it is
  reference data, not an API surface, so the generated SDK does not carry it.
- Delete the change-trend command (backed by the retired /report/* API).
- Rewrite all legacy-mock tests onto the httptest gfStub harness.
- go.mod: drop flashduty-sdk; bump go-flashduty 0.4.0 -> 0.4.1
  (adds StatusPages.ChangeActiveList).

Verified: build, vet, test, gofmt, go mod tidy clean; whoami, member list,
incident list, template get-preset/variables live-verified against api-dev.
go-flashduty v0.5.0 turns tri-state request fields into Go pointers so the
zero value (false/0) reaches the wire instead of being dropped by omitempty.
Adopt it in the CLI consumers the compiler flagged:

- alert list: --active -> IsActive=Bool(true), --recovered -> IsActive=Bool(false),
  --muted -> EverMuted=Bool(true). --recovered (is_active=false) now actually
  filters instead of being silently dropped. Also drop the dead --title no-op
  flag (no title filter on the API; 'follow the API').
- incident similar: Limit -> Int64(limit) (flag default 5, always sent).
- statuspage migrate structure: forward --url-name as URLName=String(...) when
  provided; nil reuses the source page's name. Was previously rejected because
  the SDK lacked the field; v0.5.0 adds it.

Wire-body regression tests added for alert is_active/ever_muted, the no-filter
omit case, incident similar limit, and migrate url_name forwarding.
The dual-client migration is complete; there is one client (go-flashduty).
Import it un-aliased as `flashduty`; rename newGFClient/runGFCommand/
RunContext.GFClient to newClient/runCommand/Client. Pure rename, no behavior
change.
…0.5.2

- channel list: export channelRow fields with json tags so the json/toon
  printers (reflection-based, skip unexported fields) emit full rows instead
  of empty objects.
- team list: call Teams.ReadList with the --name/--page/--limit/--orderby/
  --asc/--person-id flags instead of Teams.ReadInfos with an empty body,
  which silently dropped every flag; use server-side Total for the footer.
- go-flashduty v0.5.2: named string enums now implement fmt.Stringer, fixing
  --output-format toon for incident feed/timeline and alert timeline.
@ysyneu ysyneu marked this pull request as ready for review May 31, 2026 12:05
@ysyneu ysyneu merged commit d3ea908 into main May 31, 2026
12 checks passed
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.

1 participant