feat: add observability_pipelines resource type - #725
michael-richey wants to merge 10 commits into
Conversation
Add support for syncing Datadog Observability Pipelines via the v2 /api/v2/obs-pipelines/pipelines API: - New ObservabilityPipelines model (BaseResource subclass) with paginated LIST, GET-by-id import, POST create, PUT update, and DELETE. - Match by id (resource_mapping_key='id'), mirroring logs_metrics. The server-assigned id is excluded from create/update payloads via excluded_attributes=['id']; update_resource re-injects the destination id before the PUT. - Register the model in models/__init__.py so init_resources discovers it. - Add 'observability_pipelines' to _ID_FILE_IMPORT_SUPPORTED_TYPES so import --id-file works (default import_resource(_id=...) does a real GET and get_resources_by_ids classifies 404/429/5xx without aborting). - Unit tests cover config contract, registration, all CRUD methods, prep_resource id-stripping, no-op hooks, and id-file allowlist membership.
- Add observability_pipelines row to the supported-resources table in README.md (alphabetical, between notebooks and powerpacks). - Add integration test stub (skipped — OP pipelines hold downstream routing config, so destructive cleanup against a real org is unsafe for CI).
michael-richey
left a comment
There was a problem hiding this comment.
Thanks for pushing this — the model/test coverage is solid overall, but I found one blocking issue before we merge.
Blocking: pagination config likely mismatches OP list response shape
ObservabilityPipelines.get_resources() uses client.paginated_request(client.get) with the default PaginationConfig.
The default remaining_func in custom_client.py expects resp["meta"]["page"]["total_count"]. For Observability Pipelines, the v2 API/SDK schema exposes list metadata as meta.totalCount (not meta.page.total_count).
That means this code can work for orgs with <100 pipelines (single page), but once a page is full it may hit remaining_func and raise on the missing meta.page key, aborting import/sync for this type.
Can we switch this model to an explicit PaginationConfig for the OP response shape and add a unit test that exercises the multi-page path (not just the paginated wrapper call contract)?
The default remaining_func in custom_client.py reads resp["meta"]["page"]["total_count"], but the Observability Pipelines v2 list endpoint returns total count as meta.totalCount (camelCase). For orgs with <100 pipelines the single-page early break hides this, but a full first page would raise KeyError on the missing meta.page key, aborting import/sync for this resource type. Add a custom PaginationConfig with a remaining_func that reads meta.totalCount (with a safe fallback to 0 if meta is absent, so pagination stops gracefully). Update get_resources to pass this config explicitly. Add unit tests for the custom remaining_func (multi-page arithmetic) and for verifying get_resources passes the custom config to paginated_request.
|
Good catch — you're right that the default Fixed in 171dc9c:
All 1485 unit tests pass; |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The state-load allowlist contract is incomplete, and all API-backed integration coverage is disabled.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds Datadog Observability Pipelines as a syncable resource using the v2 API.
Changes:
- Implements list, import, create, update, and delete operations.
- Registers and documents the resource with
--id-filesupport. - Adds unit tests and a skipped integration-test stub.
| File | Description |
|---|---|
datadog_sync/model/observability_pipelines.py |
Implements the resource model and pagination. |
datadog_sync/models/__init__.py |
Registers the model. |
datadog_sync/utils/configuration.py |
Adds import ID-file support. |
README.md |
Lists the supported resource. |
tests/unit/test_observability_pipelines.py |
Tests model behavior. |
tests/unit/test_observability_pipelines_id_file.py |
Tests ID-file support. |
tests/integration/resources/test_observability_pipelines.py |
Adds a skipped integration-test stub. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Adding observability_pipelines to _ID_FILE_IMPORT_SUPPORTED_TYPES also adds it to the union _ID_FILE_SUPPORTED_TYPES, which _parse_id_file consults for both import and sync --minimize-reads --id-file. The established contract (documented in the dashboards precedent at tests/unit/test_dashboards_id_file.py:43-59) requires every ID-derivable type accepted on the state-load path to be explicitly listed in _ID_FILE_STATE_LOAD_SUPPORTED_TYPES, so the ID-derivability is audited rather than accepted incidentally via the union. The OP pipeline state key is the pipeline id (storage layout: resources/source/observability_pipelines.<id>.json), which is ID-derivable, so it qualifies. Add it to the state-load set and assert membership in a new unit test.
|
Note on the Retriggering again would occupy the queue-mode |


Summary
Adds support for syncing Datadog Observability Pipelines as a new resource type (
observability_pipelines), using the v2 API at/api/v2/obs-pipelines/pipelines.Changes
New model
datadog_sync/model/observability_pipelines.py—ObservabilityPipelines(BaseResource)with:GET /api/v2/obs-pipelines/pipelines(usesclient.paginated_request, matching the defaultPaginationConfigofpage[size]/page[number]withresponse_list_accessor="data").GET .../pipelines/{id}for the per-ID path; pass-through for the list path. Unwraps the{"data": ...}envelope.POST .../pipelineswith{"data": resource}. If the sourceidalready exists in the destination map, delegates toupdate_resource(id-keyed dedup, mirroringlogs_metrics).PUT .../pipelines/{destination_id}with{"data": resource}. Re-injects the destinationidinto the resource before the PUT (PUT, not PATCH — matches the OP API and the precedent oflogs_indexes/dashboards).DELETE .../pipelines/{destination_id}.resource_mapping_key="id",excluded_attributes=["id"](strips the server-assignedidfrom create payloads and from diff comparisons viaprep_resource).resource_connections— an OP pipeline config (sources/destinations/processors) references external systems via embedded connection settings, not sync-cli-managed Datadog resource IDs.Registration —
datadog_sync/models/__init__.pyimportsObservabilityPipelinessoinit_resourcesdiscovers it viamodels.__dict__.--id-filesupport — addedobservability_pipelinesto_ID_FILE_IMPORT_SUPPORTED_TYPES. The defaultimport_resource(_id=...)does a real GET, and the defaultget_resources_by_idsclassifies 404/429/5xx/403 without aborting (satisfies the continue-past-errors design).README — added
observability_pipelinesto the supported-resources table.Integration test stub —
tests/integration/resources/test_observability_pipelines.py, skipped (OP pipelines hold downstream routing config; destructive cleanup against a real org is unsafe for CI).Design decisions
id(notattributes.name) — mirrorslogs_metrics. Source and destination UUIDs differ, so the first sync of a pipeline always creates it on the destination; re-sync idempotency relies on persisted state. This means a pipeline deleted on the destination between syncs would be re-created rather than updated in place.PUT .../pipelines/{pipeline_id}, not PATCH.update_resourcere-injects the destinationid(stripped byprep_resource) before the PUT.--id-filestate-load allowlist entry — the state key is the pipeline id (ID-derivable), but the state-load path scopes by--resourcesintersection, not by_ID_FILE_STATE_LOAD_SUPPORTED_TYPES. This matches thelogs_metricsprecedent (also absent from the state-load set).Testing
tests/unit/test_observability_pipelines.py— config contract, registration viainit_resources, paginatedget_resources, import (pass-through + GET-by-id), create (POST + id-keyed dedup), update (PUT with destination id), delete,prep_resourceid-stripping, no-op hooks.tests/unit/test_observability_pipelines_id_file.py— allowlist membership (import set + union) andimport_resource(_id=...)GET-path verification.Test plan