Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .ddev/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ n8n = "n8n"
hpe_aruba_edgeconnect = "HPE Aruba EdgeConnect"
control_m = "Control-M"
nifi = "Apache NiFi"
dell_powerflex = "Dell Powerflex"

[overrides.metrics-prefix]
krakend = "krakend.api."
Expand All @@ -54,6 +55,7 @@ prefect = "prefect.server."
n8n = "n8n."
control_m = "control_m."
nifi = "nifi."
dell_powerflex = "dell_powerflex."
hpe_aruba_edgeconnect = "hpe_aruba_edgeconnect."

[overrides.ci.ddev]
Expand Down Expand Up @@ -204,7 +206,6 @@ kube_scheduler = "kube_scheduler"
nifi = "nifi"
nginx_ingress_controller = "nginx-ingress-controller"


[overrides.dep.updates]
exclude = [
'pyasn1', # https://github.com/pyasn1/pyasn1/issues/52
Expand Down Expand Up @@ -273,5 +274,6 @@ prefect = ["linux", "windows", "mac_os"]
n8n = ["linux", "windows", "mac_os"]
control_m = ["linux", "windows", "mac_os"]
nifi = ["linux", "windows", "mac_os"]
dell_powerflex = ["linux", "windows", "mac_os"]
hpe_aruba_edgeconnect = ["linux", "windows", "mac_os"]

21 changes: 8 additions & 13 deletions .github/chainguard/self.resolve-build-deps.push.sts.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# Policy for: .github/workflows/resolve-build-deps.yaml publish job in DataDog/integrations-core
# Triggered by push to master or release branches, or workflow_dispatch
# Triggered by push to feature branches that update dependencies
#
# Naming convention:
# self: Only this repository (DataDog/integrations-core) can use this policy
# resolve-build-deps: Specific workflow
# push: Primary trigger is push to protected branches
# push: Primary trigger is push to feature branches
#
# Security model:
# - Publish job runs on push or workflow_dispatch to protected branches (master and X.Y.x)
# - Publish job runs on push to any feature branch
# - Workflow file must be committed to the same branch
# - Pull request events are excluded by the job's if condition
# - Access is scoped to this specific workflow file via job_workflow_ref
#
# Permissions granted:
# - contents: write - Push commits to branches
# - pull_requests: write - Create pull requests
# - workflows: write - Modify workflow files in generated commits
# - contents: write - Push commits (lockfiles) back to the feature branch
#
# Usage in workflows:
# - uses: DataDog/dd-octo-sts-action@96a25462dbcb10ebf0bfd6e2ccc917d2ab235b9a # v1.0.4
Expand All @@ -24,15 +22,12 @@

issuer: https://token.actions.githubusercontent.com

subject_pattern: repo:DataDog/integrations-core:ref:refs/heads/(master|\d+\.\d+\..*)
subject_pattern: repo:DataDog/integrations-core:ref:refs/heads/.*

claim_pattern:
event_name: (push|workflow_dispatch)
job_workflow_ref: DataDog/integrations-core/\.github/workflows/resolve-build-deps\.yaml@refs/heads/(master|\d+\.\d+\..*)
ref: refs/heads/(master|\d+\.\d+\..*)
event_name: push
job_workflow_ref: DataDog/integrations-core/\.github/workflows/resolve-build-deps\.yaml@refs/heads/.*
repository: DataDog/integrations-core

permissions:
contents: write
pull_requests: write
workflows: write
4 changes: 4 additions & 0 deletions .github/workflows/config/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ integration/delinea_secret_server:
- changed-files:
- any-glob-to-any-file:
- delinea_secret_server/**/*
integration/dell_powerflex:
- changed-files:
- any-glob-to-any-file:
- dell_powerflex/**/*
integration/directory:
- changed-files:
- any-glob-to-any-file:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,26 @@ jobs:
minimum-base-package: ${{ inputs.minimum-base-package }}
pytest-args: ${{ inputs.pytest-args }}
secrets: inherit
jc346754:
uses: ./.github/workflows/test-target.yml
with:
job-name: Dell Powerflex
target: dell_powerflex
platform: linux
runner: '["ubuntu-22.04"]'
repo: "${{ inputs.repo }}"
context: ${{ inputs.context }}
python-version: "${{ inputs.python-version }}"
latest: ${{ inputs.latest }}
agent-image: "${{ inputs.agent-image }}"
agent-image-py2: "${{ inputs.agent-image-py2 }}"
agent-image-windows: "${{ inputs.agent-image-windows }}"
agent-image-windows-py2: "${{ inputs.agent-image-windows-py2 }}"
test-py2: ${{ inputs.test-py2 }}
test-py3: ${{ inputs.test-py3 }}
minimum-base-package: ${{ inputs.minimum-base-package }}
pytest-args: ${{ inputs.pytest-args }}
secrets: inherit
jc8f84c3:
uses: ./.github/workflows/test-target.yml
with:
Expand Down
3 changes: 3 additions & 0 deletions code-coverage.datadog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ services:
- id: ddev
paths:
- ddev/src/ddev/
- id: dell_powerflex
paths:
- dell_powerflex/datadog_checks/dell_powerflex/
- id: directory
paths:
- directory/datadog_checks/directory/
Expand Down
1 change: 1 addition & 0 deletions datadog_checks_base/changelog.d/23905.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``AgentCheck.submit_generic_resource`` to submit resource snapshots on the ``genresources`` event-platform track with allow-list field selection.
160 changes: 156 additions & 4 deletions datadog_checks_base/datadog_checks/base/checks/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,18 +798,170 @@ def database_monitoring_metadata(self, raw_event):
aggregator.submit_event_platform_event(self, self.check_id, to_native_string(raw_event), "dbm-metadata")

def event_platform_event(self, raw_event, event_track_type):
# type: (str, str) -> None
# type: (str | bytes, str) -> None
"""Send an event platform event.

Parameters:
raw_event (str):
JSON formatted string representing the event to send
raw_event (str | bytes):
JSON formatted string representing the event to send, or
pre-encoded bytes for proto tracks such as ``genresources``
event_track_type (str):
type of event ingested and processed by the event platform
"""
if raw_event is None:
return
aggregator.submit_event_platform_event(self, self.check_id, to_native_string(raw_event), event_track_type)
if isinstance(raw_event, (bytearray, memoryview)):
raw_event = bytes(raw_event)
elif not isinstance(raw_event, bytes):
raw_event = to_native_string(raw_event)
aggregator.submit_event_platform_event(self, self.check_id, raw_event, event_track_type)

def submit_generic_resource(self, *, type, key, fields, include, seen_at=None, expire_at=None):
# type: (str, str, dict | None, dict, int | None, int | None) -> None
"""Ship a resource on the ``genresources`` event-platform track.

``fields`` is the resource body. ``include`` chooses what to keep from it:
``{"paths": [...], "map_paths": [...], "annotation_keys": [...]}``. Evaluated against ``fields``,
``paths`` select individual values, ``map_paths`` select whole flat maps (e.g.
``metadata.labels``), and ``annotation_keys`` glob ``metadata.annotations`` keys. A path that
resolves to a structured object is dropped. Pass ``include=INCLUDE_ALL`` to ship ``fields``
as-is — only safe when your code constructed every value, never for a raw upstream object.
``seen_at`` / ``expire_at`` are optional ``int`` unix-seconds.
"""
if fields is None:
return

# stdlib json on purpose: module-level json is the orjson wrapper, which coerces datetime instead of failing.
import json as _json

# Lazy import: avoids loading the protobuf runtime for every check that imports base.py.
from datadog_checks.base.utils.genresources import (
GENRESOURCES_TRACK,
INCLUDE_ALL,
INTEGRATIONS_CORE_SOURCE,
MAX_FIELDS_JSON_BYTES,
GenericResource,
GenericResourceEvent,
apply_allow_list,
find_invalid_include,
)

integration = self.name

def _emit_dropped(count=1):
datadog_agent.emit_agent_telemetry(integration, "datadog.agent.check.genresources.dropped", count, "count")

if not key:
self.log.warning("genresources: dropping resource with empty key for type=%s", type)
_emit_dropped()
return

if not type:
self.log.warning("genresources: dropping resource with empty type for key=%s", key)
_emit_dropped()
return

if not isinstance(fields, dict):
self.log.warning(
"genresources: dropping resource with non-dict fields type=%s key=%s actual_type=%s",
type,
key,
fields.__class__.__name__,
)
_emit_dropped()
return

if include is INCLUDE_ALL:
# Caller built `fields` in code and owns its contents; ship as-is, no allow-list.
included = fields
else:
if not isinstance(include, dict):
self.log.warning(
"genresources: dropping resource with non-dict include type=%s key=%s actual_type=%s",
type,
key,
include.__class__.__name__,
)
_emit_dropped()
return

paths = include.get("paths", [])
map_paths = include.get("map_paths", [])
annotation_keys = include.get("annotation_keys", [])

def _is_str_list(value):
return isinstance(value, list) and all(isinstance(item, str) for item in value)

if not (_is_str_list(paths) and _is_str_list(map_paths) and _is_str_list(annotation_keys)):
self.log.warning("genresources: dropping resource with malformed include type=%s key=%s", type, key)
_emit_dropped()
return

if any(not pattern.strip("*?") for pattern in annotation_keys):
self.log.warning(
"genresources: dropping resource with catch-all annotation pattern type=%s key=%s", type, key
)
_emit_dropped()
return

invalid = find_invalid_include(fields, paths, map_paths)
if invalid is not None:
offending_path, reason = invalid
self.log.warning(
"genresources: dropping resource (%s) path=%s type=%s key=%s", reason, offending_path, type, key
)
_emit_dropped()
return

included = apply_allow_list(fields, paths=paths, map_paths=map_paths, annotation_keys=annotation_keys)

if not included:
self.log.warning("genresources: dropping resource with empty inclusion type=%s key=%s", type, key)
_emit_dropped()
return

try:
fields_json = _json.dumps(included, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8")
except (TypeError, ValueError):
self.log.exception("genresources: failed to encode fields for type=%s key=%s", type, key)
_emit_dropped()
return

if len(fields_json) > MAX_FIELDS_JSON_BYTES:
self.log.warning(
"genresources: dropping oversize resource type=%s key=%s size=%d",
type,
key,
len(fields_json),
)
_emit_dropped()
return

resource = GenericResource(type=type, key=key, fields_json=fields_json)

def _set_seconds(ts, value, label):
if value is None:
return
if isinstance(value, int) and not isinstance(value, bool):
ts.seconds = value
else:
self.log.warning(
"genresources: ignoring non-int %s for type=%s key=%s value=%r", label, type, key, value
)

_set_seconds(resource.seen_at, seen_at, "seen_at")
_set_seconds(resource.expire_at, expire_at, "expire_at")

event = GenericResourceEvent(source=INTEGRATIONS_CORE_SOURCE, resource=resource)
try:
payload = event.SerializeToString()
except Exception:
self.log.exception("genresources: failed to serialize type=%s key=%s", type, key)
_emit_dropped()
return

self.event_platform_event(payload, GENRESOURCES_TRACK)
datadog_agent.emit_agent_telemetry(integration, "datadog.agent.check.genresources.emitted", 1, "count")

def should_send_metric(self, metric_name):
return not self._metric_excluded(metric_name) and self._metric_included(metric_name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# (C) Datadog, Inc. 2026-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)

from .inclusion import INCLUDE_ALL, apply_allow_list, find_invalid_include
from .proto.genericresource_pb2 import GenericResource, GenericResourceEvent

GENRESOURCES_TRACK = "genresources"
INTEGRATIONS_CORE_SOURCE = "integrations-core"
MAX_FIELDS_JSON_BYTES = 1_000_000

__all__ = [
"GENRESOURCES_TRACK",
"INCLUDE_ALL",
"INTEGRATIONS_CORE_SOURCE",
"MAX_FIELDS_JSON_BYTES",
"GenericResource",
"GenericResourceEvent",
"apply_allow_list",
"find_invalid_include",
]
Loading
Loading