Skip to content

feat(integrations): migrate Datadog, Honeycomb and Coralogix onto the shared setup flow#4191

Merged
muddlebee merged 5 commits into
mainfrom
feat/setup-specs-observability
Jul 22, 2026
Merged

feat(integrations): migrate Datadog, Honeycomb and Coralogix onto the shared setup flow#4191
muddlebee merged 5 commits into
mainfrom
feat/setup-specs-observability

Conversation

@muddlebee

Copy link
Copy Markdown
Collaborator

Part of #4168.

Describe the changes you have made in this PR -

First migration batch onto the shared setup flow that #4170 introduced: Datadog, Honeycomb, Coralogix. Three near-identical shapes (API key + a couple of settings, one registered probe verifier each), picked so the batch exercises the flow without dragging in a hard case. Grafana (multi-instance, local stack, alternate auth) and Groundcover (dual env-var fallback) are deliberately left for a later batch.

What each integration gains

opensre integrations setup <service> previously wrote the integration store and stopped. That reads fine at runtime — credential resolution checks the store first — but the deploy preflight reads environment variables, so an integration you configured and verified could still block make deploy as "missing". All three now write store + keyring + .env.

Two bugs on the wizard side turned up while migrating, both now fixed by construction:

Configurator Bug
_configure_datadog called sync_env_values({})DD_API_KEY, DD_APP_KEY and DD_SITE reached no tier but the store
_configure_honeycomb wrote HONEYCOMB_DATASET + HONEYCOMB_API_URL, never HONEYCOMB_API_KEY
_configure_coralogix wrote the URL and both filters, never CORALOGIX_API_KEY

Behavior change worth calling out: setup now verifies before it persists. Previously Datadog saved and then verified, so a typo'd key overwrote a working integration and the command still printed Saved. tests/cli/test_smoke.py was pinning that old order and has been rewritten to pin the new one.

New spec capability: SetupField.default

Four of the six migrated fields prompt with a default (DD_SITE, HONEYCOMB_DATASET, both *_API_URLs). The default is applied inside apply_setup, not only offered as a prompt prefill — otherwise a surface that never prompts (the agent-driven tool in #4169) would land on different credentials than someone pressing enter at the CLI.

Guard against the failure mode this batch is most exposed to

A spec's env_var is not derivable from its credential name — base_url is written as HONEYCOMB_API_URL, endpoint as GRAFANA_INSTANCE_URL. Declare the wrong one and the value lands in .env and is silently never read back; every existing unit test still passes, because they mock the writers.

Two things close that hole:

  1. integrations/_catalog_impl.py — the reader — now imports the same config/constants/<vendor>.py names the specs write, so the two sides cannot drift for these vendors.
  2. tests/integrations/test_setup_spec_env_round_trip.py persists through the real .env writer, reloads the file into the environment, and requires load_env_integrations() to hand back the same credentials. It covers Telegram too.

I mutation-tested that guard rather than trusting a green run — changing Honeycomb's env_var to the plausible-but-wrong HONEYCOMB_BASE_URL fails it:

E   assert 'https://api.honeycomb.io' == 'https://api.eu1.honeycomb.io'
FAILED test_persisted_credentials_are_read_back_by_the_catalog[honeycomb]

Deduplication

  • surfaces/cli/wizard/configurators/spec_flow.py — the wizard's prompt-verify-retry loop, which was about to be copy-pasted a fourth time. Telegram is folded onto it, so _configure_telegram drops from 33 lines to 14.
  • validate_datadog_integration, validate_honeycomb_integration, validate_coralogix_integration deleted. Each was a line-for-line duplicate of the corresponding client.probe_access() that the registered verifier already calls — same calls, same checks, only the message wording differed. probe_access is already covered in tests/integrations/test_integration_probes.py and the per-client suites, so no coverage is lost.

Demo/Screenshot for feature changes and bug fixes -

Verify-before-persist, run against a sandboxed HOME with placeholder keys:

$ opensre integrations setup datadog

  Setting up datadog

❯ Datadog API key **********
❯ Datadog application key **********
❯ Site (e.g. datadoghq.com, datadoghq.eu) datadoghq.com

  Validating datadog credentials...
  error: Monitor API check failed: HTTP 401: {"errors":["Unauthorized"]}

$ cat $HOME/.opensre/integrations.json
cat: ...: No such file or directory     # nothing saved
$ cat $HOME/.env
cat: ...: No such file or directory     # nothing saved

Before this change the same run would have written the store and printed Saved.


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:

The problem is that three surfaces configure integrations and they disagreed about where credentials go, so whether an integration blocked make deploy depended only on which command you happened to use. #4170 built apply_setup and migrated Telegram as the reference; this PR is the first batch of the remaining 44.

The alternative I rejected was making upsert_integration mirror everything to the environment automatically. That cannot work: the store key is api_key while the env var is DD_API_KEY or HONEYCOMB_API_KEY, so a per-integration name map is unavoidable — and that map is the spec.

I also considered a bigger first batch (five integrations, adding Grafana and Groundcover). I cut it to three because Grafana carries multi-instance parsing, a local Docker stack, and username/password auth, and folding that into a batch PR would have hidden real decisions inside mechanical churn.

Key pieces:

  • integrations/<vendor>/setup.py — declares what the integration needs; no logic beyond Telegram-style resolution, which none of these three require.
  • SetupField.default — the one new spec capability, applied in the flow rather than at the prompt so every surface agrees.
  • spec_flow.configure_from_spec — the wizard's collection loop, extracted before it got copied a fourth time.
  • test_setup_spec_env_round_trip.py — the only test that can catch a wrong env_var, since the others mock the writers.

Edge cases covered: blank required field fails on that field rather than after all prompts; a field with a default is never "missing"; a submitted value beats the default; failed verification persists nothing on any tier; clearing an optional field clears every tier rather than leaving a stale .env line that would keep resolving.


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

Four of the fields in the next migration batch prompt with one today
(DD_SITE, HONEYCOMB_DATASET, and both *_API_URLs), which the spec had no
way to express.

The default is applied inside apply_setup rather than only offered as a
prompt prefill. A surface that never prompts — the wizard reusing a
stored value, or the agent-driven tool in #4169 filling fields from a
conversation — must land on the same credentials as someone pressing
enter at the CLI, and that only holds if the flow itself substitutes.

A field with a default is therefore never missing, whatever `required`
says.
… shared setup flow

First batch of #4168. These three share one shape — an API key plus a
couple of settings, one registered probe verifier each — so they exercise
the flow without dragging in a hard case. Grafana (multi-instance, local
stack, alternate auth) and Groundcover (dual env-var fallback) are left
for a later batch where they get proper attention.

`integrations setup <service>` wrote the integration store and stopped.
That reads fine at runtime, since credential resolution checks the store
first, but the deploy preflight reads environment variables — so an
integration you configured and verified could still block `make deploy`
as missing. All three now write store, keyring and .env.

The wizard was no better, in ways that were invisible until now:
_configure_datadog called sync_env_values({}) and so wrote nothing at
all, while _configure_honeycomb and _configure_coralogix wrote their
non-secret fields but dropped the API key entirely.

Setup now verifies before it persists. Datadog previously saved first, so
a typo'd key overwrote a working integration and still printed "Saved";
the smoke test pinning that order is rewritten to pin the new one.

A spec's env_var is not derivable from its credential name — base_url is
written as HONEYCOMB_API_URL, endpoint as GRAFANA_INSTANCE_URL. Declare
the wrong one and the value lands in .env and is silently never read
back, with every existing test still green because they mock the
writers. Two things close that: _catalog_impl now imports the same
config/constants/<vendor> names the specs write, and a round-trip test
persists through the real .env writer, reloads it, and requires
load_env_integrations to return the same credentials.

Also extracts the wizard's prompt-verify-retry loop to
configurators/spec_flow.py before it was copied a fourth time, and
deletes validate_{datadog,honeycomb,coralogix}_integration — each was a
line-for-line duplicate of the client's probe_access() that the
registered verifier already calls.
@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.

Comment thread tests/integrations/test_cli_spec_setup.py Fixed
Comment thread tests/integrations/test_setup_spec_env_round_trip.py Fixed
Comment thread tests/integrations/test_cli_spec_setup.py Fixed
Comment thread tests/integrations/test_setup_spec_env_round_trip.py Fixed
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates Datadog, Honeycomb, and Coralogix onto the shared apply_setup flow introduced in #4170, fixing three silent bugs where credentials were saved to the integration store but never reached the keyring or .env, which caused working integrations to be reported missing by make deploy. It also extracts the wizard's prompt-verify-retry loop into a shared configure_from_spec helper so that pattern isn't copy-pasted per vendor.

  • Verify-before-persist: all three integrations now validate credentials before writing anything; a rejected key leaves the store and .env untouched.
  • Env/keyring writes fixed: Honeycomb's API key and Coralogix's API key (previously dropped entirely) now reach sync_env_secret; Datadog's empty sync_env_values({}) call is replaced by the full field-driven persist.
  • Writer-reader drift guard: new config/constants/<vendor>.py modules are imported by both the setup specs (writer) and _catalog_impl (reader); test_setup_spec_env_round_trip.py catches any future mismatch by round-tripping through the real .env writer and asserting the catalog reconstructs the same credentials.

Confidence Score: 5/5

Safe to merge. The three integrations behave correctly on all setup surfaces and no longer silently drop credentials from the keyring and .env.

The migration correctly fixes real credential-persistence bugs for all three vendors. The shared apply_setup path enforces verify-before-persist, and the new constants modules make it impossible for the setup spec and catalog reader to silently disagree on env var names. Test coverage is thorough: parametrized unit tests per vendor, a wizard configurator unit test, and an end-to-end round-trip test that persists through the real .env writer and reloads via the catalog.

No files require special attention.

Important Files Changed

Filename Overview
integrations/setup_flow.py Adds SetupField.default field; applies the default in _collect_credentials before the required-field check, so any surface that skips prompting still lands on the documented default.
integrations/datadog/setup.py New spec declaring Datadog's three fields with correct env_var constants and defaults; wires in verify_datadog as the verifier.
integrations/honeycomb/setup.py New spec for Honeycomb; correctly declares HONEYCOMB_API_KEY_ENV as the key field's env_var — directly fixing the missing-key bug described in the PR.
integrations/coralogix/setup.py New spec for Coralogix with four fields; correctly maps base_url to CORALOGIX_API_URL, fixing the previous missing-key and wrong-env-var bugs.
surfaces/cli/wizard/configurators/spec_configurator.py Extracts the prompt-verify-retry loop shared by all spec-backed configurators; prefills from stored credentials so re-running onboarding is non-destructive.
integrations/_catalog_impl.py Replaces hard-coded string literals in Datadog/Honeycomb/Coralogix credential resolution with the shared constants modules, closing the writer-reader name drift that would have been invisible to unit tests.
tests/integrations/test_setup_spec_env_round_trip.py New end-to-end test that persists through the real .env writer and asserts the catalog reads back the exact submitted credentials; covers all three new integrations plus Telegram.
tests/integrations/test_cli_spec_setup.py Parametrized test suite covering all three vendors: prompt order, secret masking, verify-before-persist, blank required fields, and credentials reaching keyring + env, not just the store.

Sequence Diagram

sequenceDiagram
    participant CLI as opensre integrations setup
    participant Wizard as Wizard configurator
    participant Flow as apply_setup (setup_flow.py)
    participant Verifier as probe_access / verifier
    participant Store as Integration store
    participant Keyring as sync_env_secret (keyring)
    participant Env as sync_env_values (.env)

    CLI->>Flow: _run_spec_setup(spec, values)
    Wizard->>Flow: configure_from_spec(spec)
    Note over Flow: _collect_credentials applies field.default if blank
    Flow->>Verifier: spec.verify(setup, credentials)
    Verifier-->>Flow: "{status, detail}"
    alt verification failed
        Flow-->>CLI: "SetupOutcome(ok=False)"
        Flow-->>Wizard: "SetupOutcome(ok=False) re-prompt"
    else verification passed
        Flow->>Keyring: sync_env_secret(env_var, value) for each secret field
        Flow->>Env: "sync_env_values({non-secret fields})"
        Flow->>Store: upsert_integration(service, credentials)
        Flow-->>CLI: "SetupOutcome(ok=True, env_path)"
        Flow-->>Wizard: "SetupOutcome(ok=True, env_path)"
    end
Loading

Reviews (5): Last reviewed commit: "refactor(wizard): name the spec configur..." | Re-trigger Greptile

Comment thread surfaces/cli/wizard/configurators/spec_configurator.py
CodeQL py/import-and-import-from: both new test modules imported
integrations.setup_flow as a module *and* pulled IntegrationSetupSpec out
of it. Both rely on the module object for monkeypatch.setattr, so the
module import is the one to keep.
@muddlebee

Copy link
Copy Markdown
Collaborator Author

@greptile review

Greptile flagged `allow_empty=not field.required` in configure_from_spec
as misleading for a required field that carries a default, and suggested
`not field.required or bool(field.default)`.

Not taken: a defaulted field never returns blank, so claiming "empty is
acceptable" for it is less accurate, not more — _prompt_value substitutes
the default before it ever consults allow_empty, making the argument dead
for those fields either way. The real gap was that nothing said so.

Comments now explain the dead branch and the stored-value-beats-default
prefill, and tests/cli/wizard/test_spec_flow.py enforces both rather than
leaving them to a comment: a blank answer to a defaulted field is
accepted, an optional field without a default may be left empty, a stored
value is prefilled over the spec default, and a failed verification
re-asks instead of dropping the user out of onboarding.
@muddlebee

Copy link
Copy Markdown
Collaborator Author

@greptile review

1 similar comment
@muddlebee

Copy link
Copy Markdown
Collaborator Author

@greptile review

`spec_flow` sat one word away from `integrations/setup_flow` and said
nothing about which layer it was on, which is a confusing pair to leave
behind for the remaining 41 integrations.

`configurators/spec_configurator.py` matches how its siblings in that
package are named, and the docstring now states the split outright: this
module collects values from the user, setup_flow decides where they are
persisted.
@muddlebee
muddlebee merged commit 5bd93c5 into main Jul 22, 2026
27 checks passed
@muddlebee
muddlebee deleted the feat/setup-specs-observability branch July 22, 2026 10:40
@github-actions

Copy link
Copy Markdown
Contributor

😭 Clear commit message. Green tests. Kind review. @muddlebee, stop making the rest of us look bad.


👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome.

@muddlebee

Copy link
Copy Markdown
Collaborator Author

@greptile review

muddlebee added a commit that referenced this pull request Jul 22, 2026
…Vercel onto the shared setup flow (#4194)

* feat(integrations): migrate Groundcover, GitLab, Sentry, PostHog and Vercel onto the shared setup flow

Second batch of #4168, and the first that needed no new spec machinery —
everything these five required landed with #4191.

Two more wizard configurators were dropping credentials on the floor:
_configure_vercel called sync_env_values({}) so neither the token nor the
team id reached any tier but the store, and _configure_sentry wrote the
URL, org and project slugs but never SENTRY_AUTH_TOKEN to the keyring.
That is five such bugs across two batches; hand-written per-vendor
persistence was wrong more often than it was right. Both are fixed by
removing the persistence code rather than correcting it.

GitLab was worse: _setup_gitlab prompted for a URL and a token, called
upsert_integration, and stopped — no check for a blank token and no probe
at all, so a typo produced a stored integration that failed on first use.

Three of the five name their env vars differently from the credential
they carry (auth_token as GITLAB_ACCESS_TOKEN, organization_slug as
SENTRY_ORG_SLUG, base_url as SENTRY_URL), which is the mistake the
round-trip test exists to catch; it now covers all nine migrated
integrations. The readers — _catalog_impl, integrations/gitlab and
integrations/posthog/config — import the same constants the specs write,
so the two sides cannot drift.

config/constants/sentry.py and posthog.py already existed but hold
OpenSRE's *own* telemetry config: the DSN it reports its crashes to and
the write-only key for its analytics project. Those are unrelated to what
a user supplies to query their own Sentry or PostHog, so both docstrings
now say so and the integration names sit under a separate section.

Deletes the four wizard validators, whose files contained nothing else.
Three were literally build_X_config + validate_X_config, which is what
register_validation_verifier already wires up; Vercel's duplicated
client.probe_access(). Vercel's probe failure path turned out to be
untested, so tests/integrations/vercel/test_client.py gains that coverage
rather than losing it with the validator.

* style(config): annotate the new env-name constants as Final

config/constants/sentry.py and posthog.py annotate every constant they
hold, including DEFAULT_SENTRY_BASE_URL added in the same change; the new
*_ENV names did not, which reads as though they are variables.

The standalone per-vendor env modules (telegram, datadog, gitlab, …) use
no Final and stay as they are — they are consistent with each other and
with the telegram precedent. Only the two files that mix styles change.
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