Skip to content

feat(cloud): persist device-flow Postgres credentials into DVC prefs#17

Closed
jirhiker wants to merge 1 commit into
mainfrom
feat/device-code-db-creds
Closed

feat(cloud): persist device-flow Postgres credentials into DVC prefs#17
jirhiker wants to merge 1 commit into
mainfrom
feat/device-code-db-creds

Conversation

@jirhiker
Copy link
Copy Markdown

Summary

Closes the last gap in the device-flow onboarding so a technician can go from "fresh workstation" to "DVC connected" without typing, pasting, or running any CLI other than the Pychron Preferences UI.

The poll-success response now optionally carries database_url + database_role (server-side: pychronAPI PR NMGRL#42). This PR wires those through the client:

  • New pychron.cloud.dvc_credentials module — pure helpers for parse_database_urlbuild_dvc_connection_csvmerge_dvc_connection_favoritesapply_db_credentials_to_prefs.
  • WorkstationSetup carries database_url / database_role / default_metadata_repo attributes after from_device_code returns.
  • Cloud preferences pane:
    • Registered / Partial / Unregistered indicator (CustomLabel)
    • Re-registration confirmation dialog when a registration.json + keyring token already exist
    • Auto-applies DB creds to pychron.dvc.connection.favorites after success
    • Auto-runs the same whoami probe the Test Connection button uses
  • Plaintext password stripped from DeviceCodePollSuccess.raw (same defensive treatment as api_token).

Tests

  • 28 new unit tests in test/cloud/test_dvc_credentials.py (parse, build, merge, apply round-trip against a fake apptools preferences adapter).
  • 3 new tests in test/cloud/test_device_code_setup.py (db credential propagation through from_device_code, raw-field redaction).
  • Total cloud suite: 171 passing (was 140, +31 new).

Test plan

  • pytest test/cloud/ — 171 passed
  • Manual: enroll a workstation against a real bridge that has staged a DB credential; confirm pychron.dvc.connection.favorites is populated and DVC startup picks it up
  • Manual: enroll a workstation against a bridge with no staged credential; confirm enrollment still succeeds and existing DVC favorites are untouched
  • Manual: verify the "already-registered" confirmation dialog blocks an accidental re-registration

🤖 Generated with Claude Code

Closes the last gap in the device-flow onboarding so a technician can
go from "fresh workstation" to "DVC connected" without typing,
pasting, or running any CLI other than the Pychron Preferences UI:

- pychron/cloud/api_client.py — DeviceCodePollSuccess gains
  database_url and database_role slots. poll_device_code() parses
  them out of the response and strips database_url from the safe_raw
  debug-friendly dict so the embedded password cannot leak into
  caller logs.

- pychron/cloud/dvc_credentials.py (NEW) — pure helpers wrapping the
  parse → CSV → favorites round-trip:

  * parse_database_url() decomposes a postgresql:// URL into the
    fields a DVCConnectionItem needs, percent-decoding the password.

  * build_dvc_connection_csv() serializes the parsed result as a
    positional CSV that DVCConnectionItem(attrs=...) will rehydrate
    on next DVC startup. The host field carries host:port so non-
    default-port Cloud SQL connections work — the underlying CSV
    schema has no separate port attribute.

  * merge_dvc_connection_favorites() either replaces an existing
    cloud-<lab> favorite or appends a new one, and demotes any
    other row that previously held default=True so only one
    default favorite is active.

  * apply_db_credentials_to_prefs() ties it together and pushes the
    list back to pychron.dvc.connection.favorites via the standard
    apptools preferences adapter.

- pychron/cloud/workstation_setup.py — WorkstationSetup gets
  database_url, database_role, and default_metadata_repo
  attributes; from_device_code() populates them from the poll-
  success body. None on either field is a legitimate state — the
  workstation runs HTTP-only when the bridge has no staged
  credential.

- pychron/cloud/tasks/preferences.py:

  * New _registration_status field (Registered / Partial /
    Unregistered) bound to a CustomLabel so the technician sees the
    workstation's onboarding state at a glance on pane open.

  * The Start-device-code-enrollment button now refuses to proceed
    when the workstation is already onboarded (registration.json +
    keyring token both present) without an explicit confirmation
    dialog — protects against an accidental tap rotating the SSH
    keypair and burning a fresh device-code slot.

  * After a successful enrollment, _persist_db_credentials_from_setup
    writes the staged Postgres credentials into the DVC connection
    favorites and the same whoami probe the manual Test Connection
    button uses runs automatically so the technician sees an
    immediate end-to-end pass / fail without an extra click. A
    parse failure is non-fatal — surfaced via the status badge so
    the rest of the enrollment isn't rolled back.

  * Re-onboard / revoke / switch-lab buttons all refresh the
    registration status indicator on completion.

- test/cloud/test_dvc_credentials.py (NEW) — 28 unit tests covering
  parse_database_url, build_dvc_connection_csv, merge_dvc_connection_favorites,
  _row_set_field (caveman regression for the silent no-op bug), and
  apply_db_credentials_to_prefs end-to-end against a fake prefs
  adapter.

- test/cloud/test_device_code_setup.py — 3 new tests: db credential
  fields propagate to the returned WorkstationSetup, missing fields
  leave attrs at None, and the plaintext password is stripped from
  DeviceCodePollSuccess.raw the same way api_token already is.

Total: 171 cloud tests passing (was 140, +31 new).

The companion server-side staging endpoint and admin CLI live in
pychronAPI feat/workstation-db-credentials (PR NMGRL#42).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jirhiker jirhiker marked this pull request as draft May 10, 2026 18:56
@jirhiker
Copy link
Copy Markdown
Author

HOLD — server pivoting to Cloud SQL IAM auth.

This PR persists a static-password `database_url` into `pychron.dvc.connection.favorites`. The latest BDD spec requires Cloud SQL IAM auth (per-workstation GCP service account, IAM DB user, no static password). pychron's `DVCConnectionItem` already supports `connection_method=cloudsql_iam`, so the client work needed is roughly:

  • Replace `database_url` parsing with a structured payload (`instance_connection_name`, `database_name`, `iam_db_user`, `service_account_key_json`)
  • Persist the SA key JSON to `~/.pychron/keys/cloudsql_.json` (0600)
  • Populate `cloudsql_*` favorite fields instead of host/dbname/password

Held until pychronAPI ships the IAM-staging endpoint.

@jirhiker jirhiker marked this pull request as ready for review May 10, 2026 19:29
@jirhiker
Copy link
Copy Markdown
Author

Superseded by #18 (Cloud SQL IAM client path), which has merged to main. The static-password parsing + persistence this PR carries is no longer the target architecture per the latest BDD spec, and conflicts with the IAM `apply_iam_credentials_to_prefs` path that already shipped.

Closing + deleting the branch. Kept in repo history at commit `abf5e81f7` if anyone needs to read the static-password CSV / favorites round-trip patterns (which carried forward almost unchanged into #18 with the cloudsql_* fields swapped in).

@jirhiker jirhiker closed this May 10, 2026
@jirhiker jirhiker deleted the feat/device-code-db-creds branch May 10, 2026 19:35
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