feat(flagd): Flagd plugin#7753
Draft
aepfli wants to merge 19 commits into
Draft
Conversation
Exposes Flagsmith environments as flagd flag-definition documents at /api/v1/flagd/flags.json so flagd can use Flagsmith as its sync source and management UI. - Translator layer: Flagsmith segment rules → JsonLogic, multivariate options → fractional, identity overrides → targetingKey checks. - REGEX is skipped with a structured warning (no flagd equivalent). - Server-side keys only; reuses existing Last-Modified pattern and adds a strong ETag for short-circuit polls. - bootstrap_flagd_local management command for one-command local dev setup (org / project / env / server-side key, idempotent). - Documentation covering both production integration and a SQLite- backed docker-compose stack with no manual key-minting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lead with Flagsmith-as-flagd-UI rather than Flagsmith-and-flagd integration; spell out that the Flagsmith SDK is not in this topology; drop the MD5-vs-MurmurHash bucketing aside (irrelevant if you aren't mixing runtimes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds docker-compose.flagd-dev.yml plus updated docs. Brings up four containers: Postgres, Flagsmith (UI + API), a one-shot bootstrap container that mints the server-side key into a shared volume, and flagd polling Flagsmith with that key. Single `docker compose up`, no manual UI clicks to wire flagd up. Switched from SQLite to Postgres after hitting a migration that uses NOW() — Flagsmith isn't actually SQLite-compatible, only the URL parser is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flagsmith's createinitialadminuser creates the admin with password=None and only prints a reset link — DJANGO_ADMIN_PASSWORD is not a thing. Extend the bootstrap command to also create-or-update an admin user with a known password and attach it to the bootstrapped org, so the local-dev compose stack truly works with one `docker compose up` and no log-spelunking. - bootstrap_flagd_local --admin-email / --admin-password (defaults admin@example.com / admin), always re-applied on each run for predictable local dev. - Two new tests covering the admin path (fresh + refresh). - Compose: drop the misleading ALLOW_ADMIN_INITIATION_VIA_CLI / ADMIN_EMAIL / DJANGO_ADMIN_PASSWORD env block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Flagsmith image ships a HEALTHCHECK using `flagsmith healthcheck tcp`. The compose override that ran `wget -qO- /health/` never succeeded — wget isn't in the Wolfi-base image — so the flagsmith container stayed in health:starting forever and the bootstrap + flagd init steps never fired. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Flagsmith image runs as `nobody`; named volumes are created root-owned, so the bootstrap container can't write the server-key env file into /shared. Re-introduce a small busybox init service that fixes ownership before bootstrap runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The upstream flagd image is distroless — no /bin/sh — so the compose file couldn't `. /shared/flagd.env && exec flagd ...` to pick up the server-side key emitted by the bootstrap container. flagd's YAML config supports `authHeader` only (sets Authorization), not arbitrary headers like X-Environment-Key, so we can't route around the shell requirement that way either. Solution: a 3-line Dockerfile that copies the flagd binary into alpine. The compose builds it automatically on first `docker compose up`, no separate step needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The upstream flagd image ships its binary at /flagd-build, not /flagd as I assumed. Local build confirmed: launcher image now builds and the binary runs under the alpine shell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the alpine launcher workaround with the right fix: flagd 0.13
dropped --sync-provider/--sync-provider-args in favour of a JSON
--sources array, whose HTTP provider only supports the Authorization
header for auth (via authHeader). So:
- EnvironmentKeyAuthentication now also reads the env key from the
Authorization header (accepting both `Bearer <key>` and a raw token)
so flagd can authenticate via its native authHeader field.
- bootstrap_flagd_local gains --api-key, pinning the EnvironmentAPIKey
to a chosen value. The compose file uses compose interpolation
(FLAGSMITH_SERVER_KEY, defaulting to a fixed local-dev key) so the
key is known at compose-parse time and no runtime file ferrying is
needed.
- flagd uses --sources=[{...,"authHeader":"Bearer ..."}] directly with
the upstream distroless image. The launcher Dockerfile is gone.
Tests: 9 bootstrap + 10 endpoint cases (incl. Bearer/raw Authorization
fallback) all passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- flagd-sync.md: switch from --sync-provider/--sync-provider-args to --sources JSON across the bash, docker-compose, and Helm examples. Add Authorization header to the endpoint reference table. - flagd-local-dev.md: clarify that flagd uses authHeader (not X-Environment-Key) and update the unauthorized-troubleshooting note to reflect the bootstrap pin model. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variants are now {'control': <value>, ...}; 'off' is only emitted
when a segment or identity override with enabled=False needs a
destination. defaultVariant is always 'control'; flagd's state field
carries the enabled/disabled semantic.
Tests adjusted to match the new shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-change fix
Bundles the operational and authoring concerns that turned up after
the first round of end-to-end testing.
Per-project enablement
- New FlagdProjectConfiguration model + migration.
- Sync and diagnostics endpoints return 404 when not enabled
(discoverable: "this integration isn't configured" vs "denied").
- Admin toggle exposed via FlagdProjectConfigurationViewSet using the
standard ProjectIntegrationBaseViewSet convention. Registered via
one-line projects_router.register() alongside other integrations.
- bootstrap_flagd_local enables it automatically so local-dev still
works out of the box.
Diagnostics endpoint
- GET /api/v1/flagd/diagnostics.json returns a structured report of
translation warnings per feature (type mismatches, REGEX skipped,
identity-override cap hit, etc.).
- Same env-key auth as the sync endpoint; same per-project gate.
Type-mismatch detection
- New translators/type_check.py emits a warning when a flag's
control / multivariate / segment-override / identity-override
values land in different flagd typed-flag schemas (boolean / number
/ string / object / array).
- Wired into diagnose_environment so operators see the warning
before a flagd consumer hits TYPE_MISMATCH at evaluation time.
Scheduled-change cache invalidation
- Last-Modified / ETag now derive from
max(environment.updated_at, max(FeatureState.live_from <= now),
max(EnvironmentFeatureVersion.live_from <= now)).
- Scheduled v1/v2 changes propagate to flagd as soon as poll-after-
live_from fires, rather than being short-circuited by a stale 304.
Tests
- 138 passing, 4 skipped (the 4 are optional flagd-binary downloads).
- New: project-gate, admin-toggle, diagnostics endpoint, last-modified
scheduled-change behaviour, type-mismatch detection (incl. segment +
identity override coverage).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Segment and identity overrides in Flagsmith carry their own typed ``feature_state_value``. Before this commit the translator collapsed every enabled override to the parent flag's ``control`` variant and every disabled override to ``off`` — silently discarding the override's typed value. flagd targeting can only return a *variant name* (literal values are interpreted as variant keys), so preserving the value requires synthesising a per-override variant. - New ``override_<segment-slug>`` / ``override_<identifier-slug>`` variants minted for each enabled override whose value differs from control, collision-suffixed. - ``_resolve_override_variant`` now routes to that synthesised name when present; disabled overrides still route to ``off`` (disabled wins over value); same-as-control overrides still route to ``control`` (no extra variant needed). - ``_build_targeting`` prunes no-op branches where the override variant equals the fallback, so flags whose overrides happen to carry the control value still emit no targeting at all. Six new tests in ``test_override_values.py`` cover: distinct segment value, distinct identity value, value-equals-control no-op, disabled override still routing to off, slugified segment names, and two distinct overrides on one flag. Three pre-existing tests updated to reflect the new no-op pruning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously a segment/identity override with ``enabled=False`` routed to a synthesised ``off`` variant carrying a type-zero value, which required emitting the off variant on the flag and embedded an inferred 'disabled' semantic that may not have matched what the user configured. flagd has no per-segment ``state`` concept — only values. We now treat ``override.enabled`` as decorative for flagd consumers: only the override's typed ``feature_state_value`` flows through. Users who want "disabled for this segment" set the override value explicitly (e.g. ``false`` for boolean flags, ``""`` for string flags). Translator changes: - ``VARIANT_OFF`` constant removed; no off-variant synthesis. - ``_resolve_override_variant`` and the disabled-override scanning pass removed. - Disabled override whose value equals control now emits a ``disabled_override_no_op`` translation warning so the silent no-op is visible via the diagnostics endpoint. Documentation updated in the module docstring. Test fixtures across flag-translation, identity-overrides, override-values, and the real-flagd evaluation suite updated to assert the new model (147 passing, 4 skipped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
flagd's ``\$evaluators`` block is a shared keyspace — definitions
there are referenced by name from many flags. Putting every segment
there made sense when project-scoped segments were the only kind,
but Flagsmith also allows feature-scoped segments (one ``Segment``
row per feature, often loosely named). Two flags can each have a
feature-scoped segment named "mail" with completely different rules,
and dumping both into the shared keyspace forces unhelpful slug
collisions in a document operators have to read.
The decision is now usage-count-driven, keyed by segment id (never
name):
- Segment referenced by **two or more** features → extract to
``\$evaluators``, reference by ``\$ref`` from each flag.
- Segment referenced by exactly **one** feature → inline its JsonLogic
directly into the flag's ``targeting``. No name leakage into a
global keyspace; same-named feature-scoped segments stay isolated.
- Segment referenced by **zero** features → omit entirely (pre-existing
bug: we used to add unused segments to ``\$evaluators``).
Implementation:
- ``services.py::_count_segment_usage`` walks each segment's
``feature_states`` and counts distinct feature ids.
- ``services.py`` only registers ``segment_keys`` and ``evaluators``
entries for shared segments.
- ``flag.py::_build_targeting`` switches on ``segment_keys.get()`` —
shared: ``{"\$ref": key}``; single-use: the raw segment JsonLogic
inlined directly.
New unit tests cover: single-use inlining, two-feature extraction,
same-named feature-scoped segments not colliding, the 1/2/3 usage
threshold, and schema validation of an inline document. Two existing
tests updated: the integration sync test asserts inline (was asserting
``\$evaluators``); the schema test attaches the segment to two features
so it still demonstrates the extraction path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the integration docs in line with everything that landed in this branch: - Quick-start adds the per-project opt-in step (PATCH the admin toggle) before minting keys. - Document anatomy example now reflects the actual variant shape: ``control`` (no ``off``), ``variant_N`` for multivariate, ``override_<slug>`` for segment/identity overrides with distinct values. Adds a dedicated example of a flag with a segment override. - New "Variant naming" and "Segment placement" sections explain the inline-vs-extract behaviour and why there's no ``off`` variant. - Compatibility matrix updated: ``control`` instead of ``on``/``off``, single-use segments inlined, multi-use segments extracted. - Endpoint reference notes the new ``404`` status for projects where the integration isn't enabled, and the scheduled-change-aware ``Last-Modified`` / ``ETag``. - New "Diagnostics endpoint" section covering the URL, response shape, and per-warning reasons (including ``type_mismatch`` and ``disabled_override_no_op``). - New "Admin REST endpoint" section pointing operators at ``/api/v1/projects/<id>/integrations/flagd/``. - Local-dev guide note updated: bootstrap now also enables the integration on the project it provisions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@aepfli is attempting to deploy a commit to the Flagsmith Team on Vercel. A member of the Team first needs to authorize it. |
aepfli
commented
Jun 11, 2026
|
|
||
| def authenticate(self, request): # type: ignore[no-untyped-def] | ||
| api_key = request.META.get("HTTP_X_ENVIRONMENT_KEY") | ||
| if not api_key: |
Author
There was a problem hiding this comment.
i feel like i need to revert this.
…at/flagd-sync-endpoint # Conflicts: # api/poetry.lock # api/pyproject.toml
for more information, see https://pre-commit.ci
Upstream main enabled the C901 (max-complexity 10) ruff rule, which the flagd translators exceeded. Split branch collection out of _build_targeting, dispatch condition_to_jsonlogic through a per-operator handler table, and hoist detect_type_mismatch's nested helpers to module level. Also annotate the operator maps as dict[str, str] to fix the Literal-key indexing mypy errors. No behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thanks for submitting a PR! Please check the boxes below:
docs/if required so people know about the feature.Changes
This adds the possibility to utilize flagsmith as an UI for flagd
How did you test this code?
See the documentation.
If the flagd plugin is activated, there is an endpoint providing a flagd like configuration. There are some caveats, eg. there is no regex possibility in flagd. Maybe we need to more clearly structure this in the documentation.