Skip to content

feat(integrations): migrate SMTP, WhatsApp and Grafana Tempo onto the shared setup flow#4197

Merged
github-actions[bot] merged 4 commits into
mainfrom
feat/setup-specs-batch4
Jul 22, 2026
Merged

feat(integrations): migrate SMTP, WhatsApp and Grafana Tempo onto the shared setup flow#4197
github-actions[bot] merged 4 commits into
mainfrom
feat/setup-specs-batch4

Conversation

@muddlebee

Copy link
Copy Markdown
Collaborator

Part of #4168. Follows #4191 (batch 1), #4194 (batch 2), and #4196 (batch 3, still open).

Describe the changes you have made in this PR -

Fourth migration batch onto the shared setup flow: SMTP, WhatsApp, Grafana Tempo. All three are "N fields + a registered verifier" with no branching prompts, either/or groups, or env-var name mismatches.

No dropped-credential bugs this time

Tempo's wizard configurator (_configure_tempo) already wrote to the store, keyring and .env correctly — it's the first one migrated in this whole effort that had nothing wrong with it. Migrating it anyway removes the second, hand-maintained persistence implementation, which is the actual point (two implementations that happen to agree today can still drift tomorrow). SMTP and WhatsApp never had a wizard configurator at all, only opensre integrations setup <service>.

A cross-field validator already existed and needed no changes

SMTP's old CLI handler had a _die check beyond simple required/optional: if bool(username) != bool(password): _die(...). That looked like exactly the "bespoke validation" #4168 warns doesn't drop straight into a spec — except it turns out SMTPIntegrationConfig (integrations/config_models.py) already has this as a pydantic model_validator, so verify_smtp (the registered verifier) already enforces it. Nothing to add; flagging it here because I initially assumed it needed a spec feature and it didn't.

Twilio's account credentials are genuinely shared

account_sid/auth_token are one Twilio account, not per-channel — TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN are the same two env vars whatsapp and (eventually) Twilio SMS both read. config/constants/twilio.py holds them under the Twilio name for that reason, documented in both the constants module and integrations/whatsapp/setup.py's docstring, so this isn't mistaken for a naming bug later.

Found and fixed a real gap in the round-trip test itself

tests/integrations/test_setup_spec_env_round_trip.py persists through the real .env writer and asserts the catalog reads the same values back — but its fixture only ever added env vars, it never cleared what was already there. tests/conftest.py loads a contributor's real local .env into the test process for the whole session, so a spec with a wrong env_var could still "pass" if the correct name happened to already be sitting in that real .env from unrelated local setup. I hit this for real: mutation-testing WhatsApp's from_number against a deliberately wrong env name still passed, because my own machine's .env already had the correct TWILIO_WHATSAPP_FROM set from something else.

Fixed by wiping the environment before restoring only what apply_setup actually wrote (re-setting OPENSRE_DISABLE_KEYRING=1 afterward so an unset field still misses cleanly instead of touching a real OS keyring). Re-ran the WhatsApp mutation test after the fix — it now fails as expected, and the full 7-integration suite still passes clean. This hardens the guard for every integration already migrated, not just this batch.

Demo/Screenshot for feature changes and bug fixes -

Env round-trip guard catching a deliberately wrong env var, sandboxed:

$ # TWILIO_WHATSAPP_FROM_ENV pointed at "TWILIO_WA_FROM_WRONG" instead
$ pytest tests/integrations/test_setup_spec_env_round_trip.py -k whatsapp
FAILED ...test_persisted_credentials_are_read_back_by_the_catalog[whatsapp]
AssertionError: whatsapp was not discovered from the environment

Before the fixture fix, this same mutation silently passed.


Code Understanding and AI Usage

Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?

  • No, I wrote all the code myself
  • Yes, I used AI assistance (continue below)

If you used AI assistance:

  • I have reviewed every single line of the AI-generated code
  • I can explain the purpose and logic of each function/component I added
  • I have tested edge cases and understand how the code handles them
  • I have modified the AI output to follow this project's coding standards and conventions

Explain your implementation approach:

Same problem as the last three batches — three surfaces configured integrations and disagreed about where credentials land. This batch is smaller (3 vs. the previous 9) because I spent real time verifying each candidate wasn't secretly like RDS or ServiceNow (see #4196's PR body) before committing to it: checked SMTPIntegrationConfig/WhatsAppConfig/TempoConfig field names against their CLI handlers, checked env var names against integrations/_catalog_impl.py's readers, and checked for wizard configurators before assuming there wasn't one to migrate.

Key pieces:

  • integrations/{smtp,whatsapp,tempo}/setup.py — declarative field lists; no logic.
  • config/constants/{smtp,tempo,twilio}.py — env names, imported by both writer and reader.
  • Tempo's wizard configurator collapses to one line via configure_from_spec; its explanatory intro banner (bearer-token-XOR-basic-auth guidance) is preserved via the intro= parameter.

Edge cases covered: SMTP's port round-trips as a string through .env/keyring but is read back as an int (pydantic coercion) — the round-trip test now compares str(...) on both sides so that legitimate type normalization doesn't look like a persistence bug; the cross-field XOR validator on SMTP's username/password fires through the normal failed-verification path with nothing persisted; WhatsApp's optional default_to clears every tier when blanked, matching every other migrated integration.


Checklist before requesting a review

  • I have added proper PR title and linked to the issue
  • I have performed a self-review of my code
  • I can explain the purpose of every function, class, and logic block I added
  • I understand why my changes work and have tested them thoroughly
  • I have considered potential edge cases and how my code handles them
  • If it is a core feature, I have added thorough tests
  • My code follows the project's style guidelines and conventions

… shared setup flow

Fourth migration batch (#4168): three more integrations onto
IntegrationSetupSpec, none of them with name mismatches or dropped
credentials this time — Tempo's wizard configurator already persisted
correctly to all three tiers.

Also hardens tests/integrations/test_setup_spec_env_round_trip.py: the
env round-trip fixture only ever added values, so a developer's real
local .env could let a wrong env_var still "resolve" from an unrelated
pre-existing value with the same name (found via WhatsApp/Twilio's
shared TWILIO_ACCOUNT_SID/TWILIO_WHATSAPP_FROM). The fixture now wipes
the environment before restoring only what apply_setup actually wrote,
so the guard holds regardless of what's already in a contributor's .env.
@github-actions

Copy link
Copy Markdown
Contributor

Greptile code review

This repo uses Greptile for automated review. Before merge, aim for Confidence Score: 5/5 with zero unresolved review threads — see CONTRIBUTING.md.

Run a review — add a PR comment with:

@greptile review

Give it ~5-10 minutes (sometimes longer) for results, then fix feedback and re-trigger until you reach Confidence Score: 5/5.

Optional: automate with the greploop skill.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This is the fourth batch of the shared-setup-flow migration (#4168), moving SMTP, WhatsApp, and Grafana Tempo onto the shared IntegrationSetupSpec framework. Each integration gets a declarative field list in a new integrations/<service>/setup.py, a matching config/constants/<service>.py for env var names, and the old bespoke CLI handlers are collapsed to two-line _run_spec_setup delegations.

  • Tempo wizard consolidation: the hand-rolled _configure_tempo is replaced by a single configure_from_spec(TEMPO_SETUP, title=\"Tempo\", intro=_TEMPO_INTRO) call; the introductory banner is preserved via the existing intro= parameter.
  • Test fixture hardening: _restore_environment now wipes all env vars except a short OS-plumbing allowlist before seeding only what apply_setup actually wrote, closing a ghost-value loophole; validate_tempo_integration is fully removed since no remaining consumers reference it.

Confidence Score: 5/5

Safe to merge; the migration follows the established batch pattern with no dropped credentials, correct env var routing, and end-to-end round-trip test coverage for all three new integrations.

All three integrations convert cleanly — field names, env var names, default values, and secret flags each match the old hand-rolled handlers. The Tempo wizard configurator removal is fully backed by the generic spec-configurator path, and validate_tempo_integration has no remaining consumers. The round-trip test fixture is now tighter: it wipes ambient env vars before asserting, and guards against a None catalog response before comparing values.

No files require special attention.

Important Files Changed

Filename Overview
integrations/smtp/setup.py New declarative SMTP spec; field order, defaults, and secret flags match the old hand-rolled handler exactly.
integrations/tempo/setup.py New declarative Tempo spec; all five fields correctly marked optional except url, matching the old wizard configurator's behavior.
integrations/whatsapp/setup.py New declarative WhatsApp spec; account_sid/auth_token/from_number required, default_to optional; env var names delegate to config/constants/twilio.py.
config/constants/twilio.py New constants file documenting that TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN are shared across channels, not WhatsApp-specific.
surfaces/cli/wizard/configurators/observability.py Tempo wizard configurator collapsed from ~70 lines to one configure_from_spec call; intro banner preserved via the existing intro= parameter; validate_tempo_integration import removed cleanly.
surfaces/cli/wizard/integration_validators/observability.py validate_tempo_integration removed; grep confirms no remaining consumers; the wizard's own validation now flows through apply_setup via configure_from_spec.
tests/integrations/test_setup_spec_env_round_trip.py Fixture hardened with full env wipe + OPENSRE_DISABLE_KEYRING restore + actual is not None guard; three new integration entries added.
tests/integrations/test_cli_spec_setup.py Three new parametrized cases for smtp/whatsapp/tempo added; answers dicts match field names and types in the specs.
tests/cli/wizard/test_tempo_env_sync.py Deleted; the test verified the old hand-rolled _configure_tempo persistence logic which is now replaced by the generic configure_from_spec path covered by existing suite tests.
integrations/cli.py _setup_smtp, _setup_whatsapp, _setup_tempo each reduced to a two-line import + _run_spec_setup call; no behavioral change visible at the dispatch level.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph CLI["integrations/cli.py"]
        S1["_setup_smtp()"]
        S2["_setup_whatsapp()"]
        S3["_setup_tempo()"]
    end
    subgraph Specs["integrations/*/setup.py"]
        SP1["SMTP_SETUP"]
        SP2["WHATSAPP_SETUP"]
        SP3["TEMPO_SETUP"]
    end
    subgraph Flow["integrations/setup_flow.py"]
        AF["apply_setup()"]
        PE["_persist_env()"]
    end
    subgraph Storage["Persistence Tiers"]
        KR["Keyring"]
        EF[".env file"]
        ST["Integration Store"]
    end
    S1 --> SP1
    S2 --> SP2
    S3 --> SP3
    SP1 & SP2 & SP3 --> AF
    AF --> PE
    PE --> KR
    PE --> EF
    AF --> ST
Loading

Reviews (2): Last reviewed commit: "test(setup-flow): reject missing catalog..." | Re-trigger Greptile

Comment thread tests/integrations/test_setup_spec_env_round_trip.py
…ip wipe

Greptile flagged on #4197: wiping the entire environment before restoring
only what apply_setup wrote could break in the future if resolution ever
needs a system var (HOME, PATH, ...). Their suggested narrower fix —
clearing only each spec's currently-declared env_var names — would not
have caught the bug this wipe exists for: a *wrong* env_var means the
correct name is exactly the one missing from that set, so a stray real
value under the correct name would still leak through uncleared.

Keep the full wipe (it's what makes a wrong name unable to hide behind
a coincidentally-set real value), but carve out a short, explicit
allowlist of plumbing vars nothing's credential name is ever built from.
@muddlebee

Copy link
Copy Markdown
Collaborator Author

Good catch on the collateral risk, but the suggested narrower fix (clear only each spec's currently-declared field.env_var names) would not have caught the bug the wipe exists for: when the bug is a wrong env_var, the correct name is precisely the one absent from that set, so a stray real value under the correct name keeps leaking through uncleared — silently reproducing the exact false-pass this test is supposed to catch.

Kept the full wipe, but carved out a short explicit allowlist (_ENV_PLUMBING: HOME, PATH, TMPDIR/TMP/TEMP, LANG, LC_ALL, USER) that nothing's credential name is ever built from, so a future keyring backend or subprocess call needing one of those still works. Verified both the clean 7-spec run and the WhatsApp mutation-test (wrong env name) still behave correctly after the change.

@muddlebee muddlebee added the automerge Squash-merge automatically once CI checks are green label Jul 22, 2026
@muddlebee

Copy link
Copy Markdown
Collaborator Author

@greptile review

@github-actions
github-actions Bot merged commit d102b3a into main Jul 22, 2026
30 of 31 checks passed
@github-actions
github-actions Bot deleted the feat/setup-specs-batch4 branch July 22, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge Squash-merge automatically once CI checks are green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant