Skip to content

feat(experimentation): add per-organisation ingestion infrastructure service#8035

Open
gagantrivedi wants to merge 5 commits into
mainfrom
feat/experimentation-analytics-infra-service
Open

feat(experimentation): add per-organisation ingestion infrastructure service#8035
gagantrivedi wants to merge 5 commits into
mainfrom
feat/experimentation-analytics-infra-service

Conversation

@gagantrivedi

Copy link
Copy Markdown
Member

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Adds a service layer for provisioning per-organisation experiment events ingestion infrastructure, to be consumed by the events ingestion server and hooked up to an API later.

  • New experimentation/ingestion_infra_service.py: provision_ingestion_infrastructure(organisation_id) idempotently creates an S3 events bucket and a DirectPut Firehose delivery stream per organisation.
  • The stream uses dynamic partitioning on each record's environment_key, so objects land under per-environment S3 namespaces (events/env_key=.../year=.../...) and are batched per environment.
  • New settings: INGESTION_EVENTS_BUCKET_PREFIX, INGESTION_FIREHOSE_DELIVERY_ROLE_ARN (a single shared Firehose delivery role, provisioned out-of-band, scoped by bucket naming convention).

Note: with a shared delivery role, per-stream/bucket isolation is enforced by the control plane (only this service sets stream destinations), not IAM; per-organisation roles are a possible v2 hardening.

How did you test this code?

Unit tests with moto covering fresh provisioning (full destination config), idempotent re-runs, missing-settings errors, and AWS client error propagation. make lint, make typecheck, and the full experimentation unit suite pass locally.

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 20, 2026 11:43am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ignored Ignored Preview Jul 20, 2026 11:43am
flagsmith-frontend-staging Ignored Ignored Preview Jul 20, 2026 11:43am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an environment setting and immutable return type for ingestion infrastructure. Implements organisation-scoped S3 bucket and AWS Firehose delivery stream provisioning, including lifecycle, access, partitioning, compression, and error-output configuration. Updates production and staging task definitions with the delivery-role ARN, documents creation events, and adds mocked AWS tests covering validation, configuration, headers, logging, and error propagation.

Estimated code review effort: 4 (Complex) | ~45 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added api Issue related to the REST API docs Documentation updates feature New feature or request and removed docs Documentation updates labels Jul 17, 2026
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.65%. Comparing base (0838195) to head (7e3bb42).

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #8035    +/-   ##
========================================
  Coverage   98.64%   98.65%            
========================================
  Files        1506     1508     +2     
  Lines       59573    59706   +133     
========================================
+ Hits        58767    58900   +133     
  Misses        806      806            

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added the docs Documentation updates label Jul 20, 2026
@gagantrivedi
gagantrivedi force-pushed the feat/experimentation-analytics-infra-service branch from e75bdc5 to ab5cb97 Compare July 20, 2026 09:25
@github-actions github-actions Bot added feature New feature or request docs Documentation updates infrastructure and removed feature New feature or request docs Documentation updates infrastructure labels Jul 20, 2026
@gagantrivedi
gagantrivedi marked this pull request as ready for review July 20, 2026 10:46
@gagantrivedi
gagantrivedi requested review from a team as code owners July 20, 2026 10:46
@gagantrivedi
gagantrivedi requested review from germangarces and khvn26 and removed request for a team July 20, 2026 10:46
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request labels Jul 20, 2026
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-8035 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-8035 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-8035 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-8035 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-8035 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8035 Finished ✅ Results

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 24077621-5c07-4d2b-9c18-31d4b76ae2ac

📥 Commits

Reviewing files that changed from the base of the PR and between 4932b51 and 7fd6fcb.

📒 Files selected for processing (9)
  • api/app/settings/common.py
  • api/experimentation/dataclasses.py
  • api/experimentation/ingestion_infra_service.py
  • api/tests/unit/experimentation/test_ingestion_infra_service.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md
  • infrastructure/aws/production/ecs-task-definition-admin-api.json
  • infrastructure/aws/production/ecs-task-definition-task-processor.json
  • infrastructure/aws/staging/ecs-task-definition-admin-api.json
  • infrastructure/aws/staging/ecs-task-definition-task-processor.json

Comment thread api/tests/unit/experimentation/test_ingestion_infra_service.py
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Playwright Test Results (oss - depot-ubuntu-latest-16)

failed  1 failed

Details

stats  1 test across 1 suite
duration  9.6 seconds
commit  7fd6fcb
info  📦 Artifacts: View test results and HTML report
🔄 Run: #18512 (attempt 1)

Failed tests

firefox › tests/onboarding-tests.pw.ts › Onboarding › New user connects via the single-page onboarding flow @oss

### Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  6.7 seconds
commit  7fd6fcb
info  🔄 Run: #18512 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  45.1 seconds
commit  7fd6fcb
info  🔄 Run: #18512 (attempt 1)

…namespace

Derive bucket names as {prefix}-{account_id}-{region}-an and opt in via
the x-amz-bucket-namespace header, replacing the
INGESTION_EVENTS_BUCKET_PREFIX setting.
Replace the errors-only lifecycle rule with one that expires every object
in the bucket after 30 days.
…finitions

Wire the per-stack Firehose delivery role ARN into the admin-api and
task-processor task definitions for staging and production.
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

19 screenshots compared. See report for details.
View full report

@gagantrivedi
gagantrivedi force-pushed the feat/experimentation-analytics-infra-service branch from 7fd6fcb to d9291a6 Compare July 20, 2026 10:56
@github-actions github-actions Bot added docs Documentation updates infrastructure feature New feature or request and removed feature New feature or request infrastructure docs Documentation updates labels Jul 20, 2026
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #18520 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  37.4 seconds
commit  7e3bb42
info  🔄 Run: #18520 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-16 — run #18520 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  32.1 seconds
commit  7e3bb42
info  🔄 Run: #18520 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #18520 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  45.4 seconds
commit  7e3bb42
info  🔄 Run: #18520 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #18520 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  37.5 seconds
commit  7e3bb42
info  🔄 Run: #18520 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #18514 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  4 passed

Details

stats  4 tests across 4 suites
duration  36.7 seconds
commit  d9291a6
info  🔄 Run: #18514 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #18514 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  42.4 seconds
commit  d9291a6
info  🔄 Run: #18514 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #18514 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  47.4 seconds
commit  d9291a6
info  🔄 Run: #18514 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #18514 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  34.1 seconds
commit  d9291a6
info  🔄 Run: #18514 (attempt 1)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b196d699-26ee-41ce-8f5a-ea241c957100

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd6fcb and d9291a6.

📒 Files selected for processing (9)
  • api/app/settings/common.py
  • api/experimentation/dataclasses.py
  • api/experimentation/ingestion_infra_service.py
  • api/tests/unit/experimentation/test_ingestion_infra_service.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md
  • infrastructure/aws/production/ecs-task-definition-admin-api.json
  • infrastructure/aws/production/ecs-task-definition-task-processor.json
  • infrastructure/aws/staging/ecs-task-definition-admin-api.json
  • infrastructure/aws/staging/ecs-task-definition-task-processor.json

Comment on lines +19 to +26
AWS_REGION = "eu-west-2"
STREAM_NAME_PREFIX = "events-ingestion-org-"
# Buckets are created in the account regional namespace, so the name only has
# to be unique within our account and must follow the
# {prefix}-{account_id}-{region}-an convention.
BUCKET_NAME_PREFIX = "flagsmith-events-lake-org-"
BUCKET_NAMESPACE_HEADER = "x-amz-bucket-namespace"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the minimum boto3 version that supports the BucketNamespace parameter in S3 create_bucket?

💡 Result:

The BucketNamespace parameter in the S3 create_bucket API requires botocore version 1.42.0 or later to be recognized and supported [1][2][3]. This parameter was introduced to support the account-regional namespaces feature for S3 general purpose buckets, which was officially announced by AWS on March 12, 2026 [4][1][2]. As boto3 relies on botocore for its underlying API definitions and client operations, users must ensure they have a compatible version of botocore installed to utilize this feature [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== api/experimentation/ingestion_infra_service.py ==\n'
nl -ba api/experimentation/ingestion_infra_service.py | sed -n '1,140p'

printf '\n== dependency pins mentioning boto3/botocore ==\n'
rg -n --hidden --glob '!**/.git/**' '\bboto3\b|\bbotocore\b' .

Repository: Flagsmith/flagsmith

Length of output: 251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== api/experimentation/ingestion_infra_service.py ==\n'
cat -n api/experimentation/ingestion_infra_service.py | sed -n '1,160p'

printf '\n== dependency pins mentioning boto3/botocore ==\n'
rg -n --hidden --glob '!**/.git/**' '\bboto3\b|\bbotocore\b' .

Repository: Flagsmith/flagsmith

Length of output: 15528


Use the native BucketNamespace parameter once the SDK is upgraded
This shim is needed while pinned to boto3 1.35.95/botocore 1.35.95; when the pin moves to a version that supports BucketNamespace, pass it to create_bucket() directly and drop the raw x-amz-bucket-namespace header mutation.

Comment on lines +168 to +192
def _ensure_delivery_stream(
stream_name: str,
*,
bucket_name: str,
organisation_id: int,
) -> None:
try:
_get_firehose_client().create_delivery_stream(
DeliveryStreamName=stream_name,
DeliveryStreamType="DirectPut",
ExtendedS3DestinationConfiguration=_expected_destination_configuration(
bucket_name
),
)
except ClientError as exc:
if exc.response["Error"]["Code"] != "ResourceInUseException":
raise
else:
logger.info(
"ingestion_infra.stream_created",
organisation__id=organisation_id,
stream__name=stream_name,
bucket__name=bucket_name,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Firehose destination config (including the delivery role ARN) is never reconciled after first creation.

Unlike _ensure_events_bucket, which reapplies public-access-block and lifecycle settings on every call, _ensure_delivery_stream only sets ExtendedS3DestinationConfiguration at creation time; on ResourceInUseException it does nothing further. If INGESTION_FIREHOSE_DELIVERY_ROLE_ARN (or any other destination setting) is rotated later, already-provisioned organisations keep the stale config indefinitely — re-running provision_ingestion_infrastructure won't update it, and delivery will silently start failing if the old role is later revoked.

Consider calling describe_delivery_stream + update_destination when the current config differs from _expected_destination_configuration, mirroring the reconciliation already done for the bucket.

@Zaimwa9

Zaimwa9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@themis-blindfold review

Rename the _ensure_* helpers to _create_*, drop the ClientError swallow so
existing-resource errors propagate to the caller, and clarify the docstring.
The caller is responsible for tracking whether provisioning has run.
@github-actions github-actions Bot added docs Documentation updates infrastructure labels Jul 20, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request infrastructure docs Documentation updates labels Jul 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/experimentation/ingestion_infra_service.py (1)

195-200: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Handle partial provisioning failures to avoid permanently breaking retries.

Because _create_events_bucket and _create_delivery_stream unconditionally propagate all AWS client exceptions, a transient failure halfway through provisioning (for instance, if _create_delivery_stream fails after the S3 bucket is created) will cause subsequent retries to fail immediately. On the next attempt, s3.create_bucket will raise a BucketAlreadyOwnedByYou (or similar) exception, which propagates and halts execution before the stream can be created.

To ensure the provisioning workflow can safely recover from partial failures, either catch and handle "already exists" exceptions in the helper functions to restore idempotency, or explicitly check for resource existence here before attempting creation.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f42141b3-6a0e-4f6f-832c-eb7fc8397be9

📥 Commits

Reviewing files that changed from the base of the PR and between d9291a6 and 7e3bb42.

📒 Files selected for processing (3)
  • api/experimentation/ingestion_infra_service.py
  • api/tests/unit/experimentation/test_ingestion_infra_service.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md

@themis-blindfold

Copy link
Copy Markdown

⚖️ Themis judgement: ✅ Ship it

Well-structured service layer for provisioning per-organisation S3 event buckets and Firehose delivery streams. The create-then-reconcile pattern for the bucket (public access block and lifecycle reapplied on every call) is a good security habit, and the deliberate create-only semantics for the stream are documented in the docstring. Error handling correctly distinguishes "resource already exists" from real failures in both S3 and Firehose paths. Tests are thorough — happy path with full destination config verification, idempotency, header injection for account-regional namespace, settings guard, and error propagation for both AWS services.

🎯 Correctness 5/5
🧪 Test coverage 4/5
📐 Code quality 5/5
🚀 Product impact 3/5

Test coverage gets a 4 because the main integration test does heavy lifting (full destination config assertion, lifecycle, public access block, log events), but there are no direct unit tests for get_bucket_name/get_stream_name — only exercised through the integration path. Minor gap given how well the integration test covers them.

📝 Walkthrough
  • Ingestion infra service (api/experimentation/ingestion_infra_service.py) — new module that idempotently provisions an S3 events bucket (account-regional namespace, public access blocked, 30-day lifecycle) and a DirectPut Firehose delivery stream (dynamic partitioning by environment_key, GZIP, 64 MiB buffer) per organisation.
  • Dataclass (api/experimentation/dataclasses.py) — IngestionInfrastructure frozen dataclass to return bucket and stream names.
  • Settings (api/app/settings/common.py) — INGESTION_FIREHOSE_DELIVERY_ROLE_ARN env var, defaulting to empty string (service raises ImproperlyConfigured when unset).
  • Tests (api/tests/unit/experimentation/test_ingestion_infra_service.py) — six tests covering fresh provisioning, idempotency, namespace header, settings guard, and S3/Firehose error propagation via moto and mocker.
  • Infrastructure — ECS task definitions for staging and production set the new env var to the per-account Firehose delivery role ARN.
  • Docs — events catalogue updated with ingestion_infra.bucket_created and ingestion_infra.stream_created entries.
🧪 How to verify
  1. Run the experimentation unit suite: pytest api/tests/unit/experimentation/test_ingestion_infra_service.py -v — all six tests should pass.
  2. Verify the full experimentation suite still passes: pytest api/tests/unit/experimentation/ -v.
  3. Confirm the settings default is safe: start a Django shell without INGESTION_FIREHOSE_DELIVERY_ROLE_ARN set and call provision_ingestion_infrastructure(1) — it should raise ImproperlyConfigured.
  4. Inspect the ECS task definition diffs to confirm staging and production role ARNs point to the correct AWS accounts (302456015006 for staging, 084060095745 for production).

Automate: CI already covers steps 1–2 via the API Unit Tests checks (passing on 3.11, 3.12, 3.13).

Product take: Infrastructure plumbing for per-organisation experiment event ingestion — no user-visible change yet, but it lays the foundation for the experimentation data pipeline. Solid improvement to the platform's experimentation capabilities once wired up.

🧭 Assumptions & unverified claims
  • The code comment claims S3 account-regional namespace buckets must follow a {prefix}-{account_id}-{region}-an naming convention with single hyphens. Could not verify against AWS documentation whether single hyphens are accepted or double hyphens (--) are required. The x-amz-bucket-namespace: account-regional header is correctly injected, which may be sufficient regardless of name format.
  • The PR description lists INGESTION_EVENTS_BUCKET_PREFIX as a new setting, but the bucket prefix is hardcoded as BUCKET_NAME_PREFIX in the module. The description appears inaccurate on this point; the code is internally consistent.

A service that provisions its own buckets and streams — the infrastructure is bootstrapping itself, one org at a time. · reviewed at d9291a6

@Zaimwa9 Zaimwa9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 comments more than requesting changes. For the create flow, a comment would be satisfying imo.
For the tags, it looks interesting to keep track of the costs precisely

bucket_name: str,
organisation_id: int,
) -> None:
_get_firehose_client().create_delivery_stream(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that we can tag the streams https://docs.aws.amazon.com/firehose/latest/APIReference/API_CreateDeliveryStream.html

and it would directly reconcile the costs with the given organisation if my understanding is correct. Would it make sense to add them sooner than later ?
It needs to be activated in the console

return f"{STREAM_NAME_PREFIX}{organisation_id}"


def _create_events_bucket(bucket_name: str, *, organisation_id: int) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't think the comment raised by CodeRabbit has been addressed: #8035 (review)

If indeed create_bucket is create only and not upsert, we could at least skip a BucketAlreadyOwnedByYou error and resume the put operations if there had been failure during the previous creation cycle.

But I agree that we'd also need a retry mechanism somewhere and this can be a follow up. Can we in this case describe the accepted risk as a comment to not forget ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants