diff --git a/prefect/CHANGELOG.md b/prefect/CHANGELOG.md new file mode 100644 index 0000000000000..93a0d1b91c8ec --- /dev/null +++ b/prefect/CHANGELOG.md @@ -0,0 +1,4 @@ +# CHANGELOG - Prefect + + + diff --git a/prefect/README.md b/prefect/README.md new file mode 100644 index 0000000000000..a137a586caefa --- /dev/null +++ b/prefect/README.md @@ -0,0 +1,60 @@ +# Agent Check: Prefect + +## Overview + +This check monitors [Prefect][1] through the Datadog Agent. + +Include a high level overview of what this integration does: +- What does your product do (in 1-2 sentences)? +- What value will customers get from this integration, and why is it valuable to them? +- What specific data will your integration monitor, and what's the value of that data? + +## Setup + +Follow the instructions below to install and configure this check for an Agent running on a host. For containerized environments, see the [Autodiscovery Integration Templates][3] for guidance on applying these instructions. + +### Installation + +The Prefect check is included in the [Datadog Agent][2] package. +No additional installation is needed on your server. + +### Configuration + +1. Edit the `prefect.d/conf.yaml` file, in the `conf.d/` folder at the root of your Agent's configuration directory to start collecting your prefect performance data. See the [sample prefect.d/conf.yaml][4] for all available configuration options. + +2. [Restart the Agent][5]. + +### Validation + +[Run the Agent's status subcommand][6] and look for `prefect` under the Checks section. + +## Data Collected + +### Metrics + +See [metadata.csv][7] for a list of metrics provided by this integration. + +### Events + +The Prefect integration does not include any events. + +### Service Checks + +The Prefect integration does not include any service checks. + +See [service_checks.json][8] for a list of service checks provided by this integration. + +## Troubleshooting + +Need help? Contact [Datadog support][9]. + + +[1]: **LINK_TO_INTEGRATION_SITE** +[2]: https://app.datadoghq.com/account/settings/agent/latest +[3]: https://docs.datadoghq.com/agent/kubernetes/integrations/ +[4]: https://github.com/DataDog/integrations-core/blob/master/prefect/datadog_checks/prefect/data/conf.yaml.example +[5]: https://docs.datadoghq.com/agent/guide/agent-commands/#start-stop-and-restart-the-agent +[6]: https://docs.datadoghq.com/agent/guide/agent-commands/#agent-status-and-information +[7]: https://github.com/DataDog/integrations-core/blob/master/prefect/metadata.csv +[8]: https://github.com/DataDog/integrations-core/blob/master/prefect/assets/service_checks.json +[9]: https://docs.datadoghq.com/help/ diff --git a/prefect/assets/configuration/spec.yaml b/prefect/assets/configuration/spec.yaml new file mode 100644 index 0000000000000..473082f77f2b6 --- /dev/null +++ b/prefect/assets/configuration/spec.yaml @@ -0,0 +1,10 @@ +name: Prefect +files: +- name: prefect.yaml + options: + - template: init_config + options: + - template: init_config/default + - template: instances + options: + - template: instances/default diff --git a/prefect/assets/dashboards/prefect_overview.json b/prefect/assets/dashboards/prefect_overview.json new file mode 100644 index 0000000000000..e9e23301af626 --- /dev/null +++ b/prefect/assets/dashboards/prefect_overview.json @@ -0,0 +1 @@ +Please build an out-of-the-box dashboard for your integration following our best practices here: https://datadoghq.dev/integrations-core/guidelines/dashboards/#best-practices \ No newline at end of file diff --git a/prefect/assets/service_checks.json b/prefect/assets/service_checks.json new file mode 100644 index 0000000000000..fe51488c7066f --- /dev/null +++ b/prefect/assets/service_checks.json @@ -0,0 +1 @@ +[] diff --git a/prefect/changelog.d/1.added b/prefect/changelog.d/1.added new file mode 100644 index 0000000000000..aa949b47b7b41 --- /dev/null +++ b/prefect/changelog.d/1.added @@ -0,0 +1 @@ +Initial Release \ No newline at end of file diff --git a/prefect/datadog_checks/__init__.py b/prefect/datadog_checks/__init__.py new file mode 100644 index 0000000000000..a77b3f5ff63ac --- /dev/null +++ b/prefect/datadog_checks/__init__.py @@ -0,0 +1,4 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore diff --git a/prefect/datadog_checks/prefect/__about__.py b/prefect/datadog_checks/prefect/__about__.py new file mode 100644 index 0000000000000..1bde5986a04b2 --- /dev/null +++ b/prefect/datadog_checks/prefect/__about__.py @@ -0,0 +1,4 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +__version__ = '0.0.1' diff --git a/prefect/datadog_checks/prefect/__init__.py b/prefect/datadog_checks/prefect/__init__.py new file mode 100644 index 0000000000000..f144af9acba98 --- /dev/null +++ b/prefect/datadog_checks/prefect/__init__.py @@ -0,0 +1,7 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from .__about__ import __version__ +from .check import PrefectCheck + +__all__ = ['__version__', 'PrefectCheck'] diff --git a/prefect/datadog_checks/prefect/check.py b/prefect/datadog_checks/prefect/check.py new file mode 100644 index 0000000000000..0945490ed2bbc --- /dev/null +++ b/prefect/datadog_checks/prefect/check.py @@ -0,0 +1,98 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from typing import Any # noqa: F401 + +from datadog_checks.base import AgentCheck # noqa: F401 + +# from datadog_checks.base.utils.db import QueryManager +# from requests.exceptions import ConnectionError, HTTPError, InvalidURL, Timeout +# from json import JSONDecodeError + + +class PrefectCheck(AgentCheck): + + # This will be the prefix of every metric and service check the integration sends + __NAMESPACE__ = 'prefect' + + def __init__(self, name, init_config, instances): + super(PrefectCheck, self).__init__(name, init_config, instances) + + # Use self.instance to read the check configuration + # self.url = self.instance.get("url") + + # If the check is going to perform SQL queries you should define a query manager here. + # More info at + # https://datadoghq.dev/integrations-core/base/databases/#datadog_checks.base.utils.db.core.QueryManager + # sample_query = { + # "name": "sample", + # "query": "SELECT * FROM sample_table", + # "columns": [ + # {"name": "metric", "type": "gauge"} + # ], + # } + # self._query_manager = QueryManager(self, self.execute_query, queries=[sample_query]) + # self.check_initializations.append(self._query_manager.compile_queries) + + def check(self, _): + # type: (Any) -> None + # The following are useful bits of code to help new users get started. + + # Perform HTTP Requests with our HTTP wrapper. + # More info at https://datadoghq.dev/integrations-core/base/http/ + # try: + # response = self.http.get(self.url) + # response.raise_for_status() + # response_json = response.json() + + # except Timeout as e: + # self.service_check( + # "can_connect", + # AgentCheck.CRITICAL, + # message="Request timeout: {}, {}".format(self.url, e), + # ) + # raise + + # except (HTTPError, InvalidURL, ConnectionError) as e: + # self.service_check( + # "can_connect", + # AgentCheck.CRITICAL, + # message="Request failed: {}, {}".format(self.url, e), + # ) + # raise + + # except JSONDecodeError as e: + # self.service_check( + # "can_connect", + # AgentCheck.CRITICAL, + # message="JSON Parse failed: {}, {}".format(self.url, e), + # ) + # raise + + # except ValueError as e: + # self.service_check( + # "can_connect", AgentCheck.CRITICAL, message=str(e) + # ) + # raise + + # This is how you submit metrics + # There are different types of metrics that you can submit (gauge, event). + # More info at https://datadoghq.dev/integrations-core/base/api/#datadog_checks.base.checks.base.AgentCheck + # self.gauge("test", 1.23, tags=['foo:bar']) + + # Perform database queries using the Query Manager + # self._query_manager.execute() + + # This is how you use the persistent cache. This cache file based and persists across agent restarts. + # If you need an in-memory cache that is persisted across runs + # You can define a dictionary in the __init__ method. + # self.write_persistent_cache("key", "value") + # value = self.read_persistent_cache("key") + + # If your check ran successfully, you can send the status. + # More info at + # https://datadoghq.dev/integrations-core/base/api/#datadog_checks.base.checks.base.AgentCheck.service_check + # self.service_check("can_connect", AgentCheck.OK) + + # If it didn't then it should send a critical service check + self.service_check("can_connect", AgentCheck.CRITICAL) diff --git a/prefect/datadog_checks/prefect/config_models/__init__.py b/prefect/datadog_checks/prefect/config_models/__init__.py new file mode 100644 index 0000000000000..520514da4d96e --- /dev/null +++ b/prefect/datadog_checks/prefect/config_models/__init__.py @@ -0,0 +1,25 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + + +from .instance import InstanceConfig +from .shared import SharedConfig + + +class ConfigMixin: + _config_model_instance: InstanceConfig + _config_model_shared: SharedConfig + + @property + def config(self) -> InstanceConfig: + return self._config_model_instance + + @property + def shared_config(self) -> SharedConfig: + return self._config_model_shared diff --git a/prefect/datadog_checks/prefect/config_models/defaults.py b/prefect/datadog_checks/prefect/config_models/defaults.py new file mode 100644 index 0000000000000..2941cd3063c30 --- /dev/null +++ b/prefect/datadog_checks/prefect/config_models/defaults.py @@ -0,0 +1,16 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + + +def instance_empty_default_hostname(): + return False + + +def instance_min_collection_interval(): + return 15 diff --git a/prefect/datadog_checks/prefect/config_models/instance.py b/prefect/datadog_checks/prefect/config_models/instance.py new file mode 100644 index 0000000000000..70e1f0402827e --- /dev/null +++ b/prefect/datadog_checks/prefect/config_models/instance.py @@ -0,0 +1,51 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + + +from __future__ import annotations + +from typing import Optional + +from pydantic import BaseModel, ConfigDict, field_validator, model_validator + +from datadog_checks.base.utils.functions import identity +from datadog_checks.base.utils.models import validation + +from . import defaults, validators + + +class InstanceConfig(BaseModel): + model_config = ConfigDict( + validate_default=True, + arbitrary_types_allowed=True, + frozen=True, + ) + empty_default_hostname: Optional[bool] = None + min_collection_interval: Optional[float] = None + service: Optional[str] = None + tags: Optional[tuple[str, ...]] = None + + @model_validator(mode='before') + def _initial_validation(cls, values): + return validation.core.initialize_config(getattr(validators, 'initialize_instance', identity)(values)) + + @field_validator('*', mode='before') + def _validate(cls, value, info): + field = cls.model_fields[info.field_name] + field_name = field.alias or info.field_name + if field_name in info.context['configured_fields']: + value = getattr(validators, f'instance_{info.field_name}', identity)(value, field=field) + else: + value = getattr(defaults, f'instance_{info.field_name}', lambda: value)() + + return validation.utils.make_immutable(value) + + @model_validator(mode='after') + def _final_validation(cls, model): + return validation.core.check_model(getattr(validators, 'check_instance', identity)(model)) diff --git a/prefect/datadog_checks/prefect/config_models/shared.py b/prefect/datadog_checks/prefect/config_models/shared.py new file mode 100644 index 0000000000000..70e2e73499c72 --- /dev/null +++ b/prefect/datadog_checks/prefect/config_models/shared.py @@ -0,0 +1,48 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + + +from __future__ import annotations + +from typing import Optional + +from pydantic import BaseModel, ConfigDict, field_validator, model_validator + +from datadog_checks.base.utils.functions import identity +from datadog_checks.base.utils.models import validation + +from . import defaults, validators + + +class SharedConfig(BaseModel): + model_config = ConfigDict( + validate_default=True, + arbitrary_types_allowed=True, + frozen=True, + ) + service: Optional[str] = None + + @model_validator(mode='before') + def _initial_validation(cls, values): + return validation.core.initialize_config(getattr(validators, 'initialize_shared', identity)(values)) + + @field_validator('*', mode='before') + def _validate(cls, value, info): + field = cls.model_fields[info.field_name] + field_name = field.alias or info.field_name + if field_name in info.context['configured_fields']: + value = getattr(validators, f'shared_{info.field_name}', identity)(value, field=field) + else: + value = getattr(defaults, f'shared_{info.field_name}', lambda: value)() + + return validation.utils.make_immutable(value) + + @model_validator(mode='after') + def _final_validation(cls, model): + return validation.core.check_model(getattr(validators, 'check_shared', identity)(model)) diff --git a/prefect/datadog_checks/prefect/config_models/validators.py b/prefect/datadog_checks/prefect/config_models/validators.py new file mode 100644 index 0000000000000..8495a481e5308 --- /dev/null +++ b/prefect/datadog_checks/prefect/config_models/validators.py @@ -0,0 +1,13 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# Here you can include additional config validators or transformers +# +# def initialize_instance(values, **kwargs): +# if 'my_option' not in values and 'my_legacy_option' in values: +# values['my_option'] = values['my_legacy_option'] +# if values.get('my_number') > 10: +# raise ValueError('my_number max value is 10, got %s' % str(values.get('my_number'))) +# +# return values diff --git a/prefect/datadog_checks/prefect/data/conf.yaml.example b/prefect/datadog_checks/prefect/data/conf.yaml.example new file mode 100644 index 0000000000000..8ee633b1335fc --- /dev/null +++ b/prefect/datadog_checks/prefect/data/conf.yaml.example @@ -0,0 +1,44 @@ +## All options defined here are available to all instances. +# +init_config: + + ## @param service - string - optional + ## Attach the tag `service:` to every metric, event, and service check emitted by this integration. + ## + ## Additionally, this sets the default `service` for every log source. + # + # service: + +## Every instance is scheduled independently of the others. +# +instances: + + - + ## @param tags - list of strings - optional + ## A list of tags to attach to every metric and service check emitted by this instance. + ## + ## Learn more about tagging at https://docs.datadoghq.com/tagging + # + # tags: + # - : + # - : + + ## @param service - string - optional + ## Attach the tag `service:` to every metric, event, and service check emitted by this integration. + ## + ## Overrides any `service` defined in the `init_config` section. + # + # service: + + ## @param min_collection_interval - number - optional - default: 15 + ## This changes the collection interval of the check. For more information, see: + ## https://docs.datadoghq.com/developers/write_agent_check/#collection-interval + # + # min_collection_interval: 15 + + ## @param empty_default_hostname - boolean - optional - default: false + ## This forces the check to send metrics with no hostname. + ## + ## This is useful for cluster-level checks. + # + # empty_default_hostname: false diff --git a/prefect/hatch.toml b/prefect/hatch.toml new file mode 100644 index 0000000000000..c85c5f07a7df2 --- /dev/null +++ b/prefect/hatch.toml @@ -0,0 +1,4 @@ +[env.collectors.datadog-checks] + +[[envs.default.matrix]] +python = ["3.12"] diff --git a/prefect/images/IMAGES_README.md b/prefect/images/IMAGES_README.md new file mode 100644 index 0000000000000..443f3c45e3385 --- /dev/null +++ b/prefect/images/IMAGES_README.md @@ -0,0 +1,41 @@ +# Marketplace Media Carousel Guidelines + +## Using the media gallery + +Please upload images to use the media gallery. Integrations require a minimum of 3 images. Images should highlight your product, your integration, and a full image of the Datadog integration dashboard. The gallery +can hold a maximum of 8 pieces of media total, and one of these pieces of media +can be a video (guidelines and submission steps below). Images should be +added to your /images directory and referenced in the manifest.json file. + + +## Image and video requirements + +### Images + +``` +File type : .jpg or .png +File size : ~500 KB per image, with a max of 1 MB per image +File dimensions : The image must be between 1440px and 2880px width, with a 16:9 aspect ratio (for example: 1440x810) +File name : Use only letters, numbers, underscores, and hyphens +Color mode : RGB +Color profile : sRGB +Description : 300 characters maximum +``` + +### Video + +To display a video in your media gallery, please send our team the zipped file +or a link to download the video at `marketplace@datadog.com`. In addition, +please upload a thumbnail image for your video as a part of the pull request. +Once approved, we will upload the file to Vimeo and provide you with the +vimeo_id to add to your manifest.json file. Please note that the gallery can +only hold one video. + +``` +File type : MP4 H.264 +File size : Max 1 video; 1 GB maximum size +File dimensions : The aspect ratio must be exactly 16:9, and the resolution must be 1920x1080 or higher +File name : partnerName-appName.mp4 +Run time : Recommendation of 60 seconds or less +Description : 300 characters maximum +``` diff --git a/prefect/manifest.json b/prefect/manifest.json new file mode 100644 index 0000000000000..ad8b12643fcc6 --- /dev/null +++ b/prefect/manifest.json @@ -0,0 +1,57 @@ +{ + "manifest_version": "2.0.0", + "app_uuid": "cf10fa49-7df2-4b53-945d-7e4d63643efe", + "app_id": "prefect", + "display_on_public_website": false, + "tile": { + "overview": "README.md#Overview", + "configuration": "README.md#Setup", + "support": "README.md#Support", + "changelog": "CHANGELOG.md", + "description": "A Python-first workflow orchestration platform for data, ML, and automation", + "title": "Prefect", + "media": [], + "classifier_tags": [ + "Supported OS::Linux", + "Supported OS::Windows", + "Supported OS::macOS", + "Category::Orchestration", + "Offering::Integration", + "Submitted Data Type::Metrics", + "Submitted Data Type::Logs" + ] + }, + "assets": { + "integration": { + "auto_install": true, + "source_type_id": 61657161, + "source_type_name": "Prefect", + "configuration": { + "spec": "assets/configuration/spec.yaml" + }, + "events": { + "creates_events": false + }, + "metrics": { + "prefix": "prefect.io.", + "check": "", + "metadata_path": "metadata.csv" + }, + "service_checks": { + "metadata_path": "assets/service_checks.json" + }, + "process_signatures": [ + "prefect" + ] + }, + "dashboards": {}, + "monitors": {}, + "saved_views": {} + }, + "author": { + "support_email": "help@datadoghq.com", + "name": "Datadog", + "homepage": "https://www.datadoghq.com", + "sales_email": "info@datadoghq.com" + } +} diff --git a/prefect/metadata.csv b/prefect/metadata.csv new file mode 100644 index 0000000000000..02cde5e98381e --- /dev/null +++ b/prefect/metadata.csv @@ -0,0 +1 @@ +metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name,curated_metric,sample_tags diff --git a/prefect/pyproject.toml b/prefect/pyproject.toml new file mode 100644 index 0000000000000..29a9ebf529852 --- /dev/null +++ b/prefect/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = [ + "hatchling>=0.13.0", +] +build-backend = "hatchling.build" + +[project] +name = "datadog-prefect" +description = "The Prefect check" +readme = "README.md" +license = "BSD-3-Clause" +requires-python = ">=3.12" +keywords = [ + "datadog", + "datadog agent", + "datadog check", + "prefect", +] +authors = [ + { name = "Datadog", email = "packages@datadoghq.com" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: BSD License", + "Private :: Do Not Upload", + "Programming Language :: Python :: 3.12", + "Topic :: System :: Monitoring", +] +dependencies = [ + "datadog-checks-base>=37.0.0", +] +dynamic = [ + "version", +] + +[project.optional-dependencies] +deps = [] + +[project.urls] +Source = "https://github.com/DataDog/integrations-core" + +[tool.hatch.version] +path = "datadog_checks/prefect/__about__.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/datadog_checks", + "/tests", + "/manifest.json", +] + +[tool.hatch.build.targets.wheel] +include = [ + "/datadog_checks/prefect", +] +dev-mode-dirs = [ + ".", +] diff --git a/prefect/tests/__init__.py b/prefect/tests/__init__.py new file mode 100644 index 0000000000000..c9f1f2a9882c7 --- /dev/null +++ b/prefect/tests/__init__.py @@ -0,0 +1,3 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) diff --git a/prefect/tests/conftest.py b/prefect/tests/conftest.py new file mode 100644 index 0000000000000..1423666ef6381 --- /dev/null +++ b/prefect/tests/conftest.py @@ -0,0 +1,14 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import pytest + + +@pytest.fixture(scope='session') +def dd_environment(): + yield + + +@pytest.fixture +def instance(): + return {} diff --git a/prefect/tests/test_unit.py b/prefect/tests/test_unit.py new file mode 100644 index 0000000000000..081f1ab195b75 --- /dev/null +++ b/prefect/tests/test_unit.py @@ -0,0 +1,26 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +from typing import Any, Callable, Dict # noqa: F401 + +from datadog_checks.base import AgentCheck # noqa: F401 +from datadog_checks.base.stubs.aggregator import AggregatorStub # noqa: F401 +from datadog_checks.dev.utils import get_metadata_metrics +from datadog_checks.prefect import PrefectCheck + + +def test_check(dd_run_check, aggregator, instance): + # type: (Callable[[AgentCheck, bool], None], AggregatorStub, Dict[str, Any]) -> None + check = PrefectCheck('prefect', {}, [instance]) + dd_run_check(check) + + aggregator.assert_all_metrics_covered() + aggregator.assert_metrics_using_metadata(get_metadata_metrics()) + + +def test_emits_critical_service_check_when_service_is_down(dd_run_check, aggregator, instance): + # type: (Callable[[AgentCheck, bool], None], AggregatorStub, Dict[str, Any]) -> None + check = PrefectCheck('prefect', {}, [instance]) + dd_run_check(check) + aggregator.assert_service_check('prefect.can_connect', PrefectCheck.CRITICAL)