From dc2090d0189ef798ff4651ffb80e496856915c4d Mon Sep 17 00:00:00 2001 From: Sherzod K Date: Tue, 22 Feb 2022 15:45:15 -0500 Subject: [PATCH 1/3] TypeConfiguration support and handle resource id from v2.x.x format --- ...atadog-monitors-monitor-configuration.json | 158 ++++++++++++++++++ .../datadog-monitors-monitor.json | 30 +++- .../docs/README.md | 2 +- .../requirements.txt | 2 +- .../src/datadog_monitors_monitor/handlers.py | 75 +++++---- .../src/datadog_monitors_monitor/models.py | 23 ++- 6 files changed, 245 insertions(+), 45 deletions(-) create mode 100644 datadog-monitors-monitor-handler/datadog-monitors-monitor-configuration.json diff --git a/datadog-monitors-monitor-handler/datadog-monitors-monitor-configuration.json b/datadog-monitors-monitor-handler/datadog-monitors-monitor-configuration.json new file mode 100644 index 00000000..c01e37b2 --- /dev/null +++ b/datadog-monitors-monitor-handler/datadog-monitors-monitor-configuration.json @@ -0,0 +1,158 @@ +{ + "properties": { + "DatadogCredentials": { + "$ref": "#/definitions/DatadogCredentials" + } + }, + "additionalProperties": false, + "definitions": { + "Creator": { + "type": "object", + "properties": { + "Name": { + "description": "Name of the creator of the monitor", + "type": "string" + }, + "Handle": { + "description": "Handle of the creator of the monitor", + "type": "string" + }, + "Email": { + "description": "Email of the creator of the monitor", + "type": "string" + } + } + }, + "MonitorThresholds": { + "type": "object", + "properties": { + "Critical": { + "description": "Threshold value for triggering an alert", + "type": "number" + }, + "CriticalRecovery": { + "description": "Threshold value for recovering from an alert state", + "type": "number" + }, + "OK": { + "description": "Threshold value for recovering from an alert state", + "type": "number" + }, + "Warning": { + "description": "Threshold value for triggering a warning", + "type": "number" + }, + "WarningRecovery": { + "description": "Threshold value for recovering from a warning state", + "type": "number" + } + } + }, + "MonitorThresholdWindows": { + "type": "object", + "properties": { + "TriggerWindow": { + "description": "How long a metric must be anomalous before triggering an alert", + "type": "string" + }, + "RecoveryWindow": { + "description": "How long an anomalous metric must be normal before recovering from an alert state", + "type": "string" + } + } + }, + "MonitorOptions": { + "type": "object", + "properties": { + "EnableLogsSample": { + "description": "Whether or not to include a sample of the logs", + "type": "boolean" + }, + "EscalationMessage": { + "description": "Message to include with a re-notification when renotify_interval is set", + "type": "string" + }, + "EvaluationDelay": { + "description": "Time in seconds to delay evaluation", + "type": "integer" + }, + "IncludeTags": { + "description": "Whether or not to include triggering tags into notification title", + "type": "boolean" + }, + "Locked": { + "description": "Whether or not changes to this monitor should be restricted to the creator or admins", + "type": "boolean" + }, + "MinLocationFailed": { + "description": "Number of locations allowed to fail before triggering alert", + "type": "integer" + }, + "NewHostDelay": { + "description": "Time in seconds to allow a host to start reporting data before starting the evaluation of monitor results", + "type": "integer" + }, + "NoDataTimeframe": { + "description": "Number of minutes data stopped reporting before notifying", + "type": "integer" + }, + "NotifyAudit": { + "description": "Whether or not to notify tagged users when changes are made to the monitor", + "type": "boolean" + }, + "NotifyNoData": { + "description": "Whether or not to notify when data stops reporting", + "type": "boolean" + }, + "RenotifyInterval": { + "description": "Number of minutes after the last notification before the monitor re-notifies on the current status", + "type": "integer" + }, + "RequireFullWindow": { + "description": "Whether or not the monitor requires a full window of data before it is evaluated", + "type": "boolean" + }, + "SyntheticsCheckID": { + "description": "ID of the corresponding synthetics check", + "type": "integer" + }, + "Thresholds": { + "description": "The threshold definitions", + "$ref": "#/definitions/MonitorThresholds" + }, + "ThresholdWindows": { + "description": "The threshold window definitions", + "$ref": "#/definitions/MonitorThresholdWindows" + }, + "TimeoutH": { + "description": "Number of hours of the monitor not reporting data before it automatically resolves", + "type": "integer" + } + } + }, + "DatadogCredentials": { + "description": "Credentials for the Datadog API", + "properties": { + "ApiKey": { + "description": "Datadog API key", + "type": "string" + }, + "ApplicationKey": { + "description": "Datadog application key", + "type": "string" + }, + "ApiURL": { + "description": "Datadog API URL (defaults to https://api.datadoghq.com) Use https://api.datadoghq.eu for EU accounts.", + "type": "string" + } + }, + "required": [ + "ApiKey", + "ApplicationKey" + ], + "type": "object", + "additionalProperties": false + } + }, + "typeName": "Datadog::Monitors::Monitor" +} diff --git a/datadog-monitors-monitor-handler/datadog-monitors-monitor.json b/datadog-monitors-monitor-handler/datadog-monitors-monitor.json index 96b0e3c0..d46b1123 100644 --- a/datadog-monitors-monitor-handler/datadog-monitors-monitor.json +++ b/datadog-monitors-monitor-handler/datadog-monitors-monitor.json @@ -1,6 +1,14 @@ { "typeName": "Datadog::Monitors::Monitor", "description": "Datadog Monitor 3.0.0", + "typeConfiguration": { + "properties": { + "DatadogCredentials": { + "$ref": "#/definitions/DatadogCredentials" + } + }, + "additionalProperties": false + }, "definitions": { "Creator": { "type": "object", @@ -125,9 +133,7 @@ "type": "integer" } } - } - }, - "properties": { + }, "DatadogCredentials": { "description": "Credentials for the Datadog API", "properties": { @@ -148,14 +154,27 @@ "ApiKey", "ApplicationKey" ], - "type": "object" + "type": "object", + "additionalProperties": false + } + }, + "properties": { + "DatadogCredentials": { + "$ref": "#/definitions/DatadogCredentials" }, "Creator": { "$ref": "#/definitions/Creator" }, "Id": { "description": "ID of the monitor", - "type": "integer" + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] }, "Message": { "description": "A message to include with notifications for the monitor", @@ -216,7 +235,6 @@ } }, "required": [ - "DatadogCredentials", "Query", "Type" ], diff --git a/datadog-monitors-monitor-handler/docs/README.md b/datadog-monitors-monitor-handler/docs/README.md index b043f7b8..6b8e76a7 100644 --- a/datadog-monitors-monitor-handler/docs/README.md +++ b/datadog-monitors-monitor-handler/docs/README.md @@ -46,7 +46,7 @@ Properties: Credentials for the Datadog API -_Required_: Yes +_Required_: No _Type_: DatadogCredentials diff --git a/datadog-monitors-monitor-handler/requirements.txt b/datadog-monitors-monitor-handler/requirements.txt index 6e6b236e..1c6e42f4 100644 --- a/datadog-monitors-monitor-handler/requirements.txt +++ b/datadog-monitors-monitor-handler/requirements.txt @@ -1 +1 @@ -git+https://github.com/datadog/datadog-cloudformation-resources.git@datadog-cloudformation-common-python-0.0.4#egg=datadog_cloudformation_common&subdirectory=datadog-cloudformation-common-python +git+https://github.com/datadog/datadog-cloudformation-resources.git@datadog-cloudformation-common-python-0.0.6#egg=datadog_cloudformation_common&subdirectory=datadog-cloudformation-common-python diff --git a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/handlers.py b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/handlers.py index 51a30bb4..963616ec 100644 --- a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/handlers.py +++ b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/handlers.py @@ -1,5 +1,5 @@ import logging -from typing import Any, MutableMapping, Optional +from typing import Any, MutableMapping, Optional, Union, Tuple from cloudformation_cli_python_lib import ( Action, @@ -23,7 +23,9 @@ from .models import Creator, MonitorOptions, MonitorThresholdWindows, \ MonitorThresholds, \ ResourceHandlerRequest, \ - ResourceModel + ResourceModel, \ + DatadogCredentials, \ + TypeConfigurationModel from .version import __version__ # Use this logger to forward log messages to CloudWatch Logs. @@ -31,7 +33,7 @@ TYPE_NAME = "Datadog::Monitors::Monitor" TELEMETRY_TYPE_NAME = "monitors-monitor" -resource = Resource(TYPE_NAME, ResourceModel) +resource = Resource(TYPE_NAME, ResourceModel, TypeConfigurationModel) test_entrypoint = resource.test_entrypoint @@ -43,15 +45,11 @@ def read_handler( ) -> ProgressEvent: LOG.info("Starting %s Read Handler", TYPE_NAME) model = request.desiredResourceState - with v1_client( - model.DatadogCredentials.ApiKey, - model.DatadogCredentials.ApplicationKey, - model.DatadogCredentials.ApiURL or "https://api.datadoghq.com", - TELEMETRY_TYPE_NAME, - __version__, - ) as api_client: + + api_key, app_key, api_url = get_auth(model.DatadogCredentials, request.typeConfiguration) + with v1_client(api_key, app_key, api_url, TELEMETRY_TYPE_NAME, __version__) as api_client: api_instance = MonitorsApi(api_client) - monitor_id = model.Id + monitor_id = get_id(model.Id) try: monitor = api_instance.get_monitor(monitor_id) except ApiException as e: @@ -115,7 +113,7 @@ def read_handler( TriggerWindow=tw.trigger_window if hasattr(tw, "trigger_window") else None, RecoveryWindow=tw.recovery_window if hasattr(tw, "recovery_window") else None, ) - model.Id = monitor.id + model.Id = get_id(monitor.id) return ProgressEvent( status=OperationStatus.SUCCESS, @@ -145,16 +143,11 @@ def update_handler( if options: monitor.options = options - with v1_client( - model.DatadogCredentials.ApiKey, - model.DatadogCredentials.ApplicationKey, - model.DatadogCredentials.ApiURL or "https://api.datadoghq.com", - TELEMETRY_TYPE_NAME, - __version__, - ) as api_client: + api_key, app_key, api_url = get_auth(model.DatadogCredentials, request.typeConfiguration) + with v1_client(api_key, app_key, api_url, TELEMETRY_TYPE_NAME, __version__) as api_client: api_instance = MonitorsApi(api_client) try: - api_instance.update_monitor(model.Id, monitor) + api_instance.update_monitor(get_id(model.Id), monitor) except ApiException as e: LOG.error("Exception when calling MonitorsApi->update_monitor: %s\n", e) return ProgressEvent( @@ -173,16 +166,11 @@ def delete_handler( LOG.info("Starting %s Delete Handler", TYPE_NAME) model = request.desiredResourceState - with v1_client( - model.DatadogCredentials.ApiKey, - model.DatadogCredentials.ApplicationKey, - model.DatadogCredentials.ApiURL or "https://api.datadoghq.com", - TELEMETRY_TYPE_NAME, - __version__, - ) as api_client: + api_key, app_key, api_url = get_auth(model.DatadogCredentials, request.typeConfiguration) + with v1_client(api_key, app_key, api_url, TELEMETRY_TYPE_NAME, __version__) as api_client: api_instance = MonitorsApi(api_client) try: - api_instance.delete_monitor(model.Id) + api_instance.delete_monitor(get_id(model.Id)) except ApiException as e: LOG.error("Exception when calling MonitorsApi->delete_monitor: %s\n", e) return ProgressEvent( @@ -217,13 +205,8 @@ def create_handler( if options: monitor.options = options - with v1_client( - model.DatadogCredentials.ApiKey, - model.DatadogCredentials.ApplicationKey, - model.DatadogCredentials.ApiURL or "https://api.datadoghq.com", - TELEMETRY_TYPE_NAME, - __version__, - ) as api_client: + api_key, app_key, api_url = get_auth(model.DatadogCredentials, request.typeConfiguration) + with v1_client(api_key, app_key, api_url, TELEMETRY_TYPE_NAME, __version__) as api_client: api_instance = MonitorsApi(api_client) try: monitor_resp = api_instance.create_monitor(monitor) @@ -233,7 +216,7 @@ def create_handler( status=OperationStatus.FAILED, resourceModel=model, message=f"Error creating monitor: {e}" ) - model.Id = monitor_resp.id + model.Id = get_id(monitor_resp.id) return read_handler(session, request, callback_context) @@ -298,3 +281,23 @@ def build_monitor_options_from_model(model: ResourceModel) -> ApiMonitorOptions: options.threshold_windows.recovery_window = model.Options.ThresholdWindows.RecoveryWindow return options + + +def get_id(_id: Union[str, int]) -> int: + if isinstance(_id, str): + return int(float(_id)) + return _id + + +def get_auth( + dd_credentials: Optional[DatadogCredentials], + type_configuration: Optional[TypeConfigurationModel] +) -> Tuple[str, str, str]: + if dd_credentials: + return dd_credentials.ApiKey, \ + dd_credentials.ApplicationKey, \ + dd_credentials.ApiURL or "https://api.datadoghq.com" + else: + return type_configuration.DatadogCredentials.ApiKey, \ + type_configuration.DatadogCredentials.ApplicationKey, \ + type_configuration.DatadogCredentials.ApiURL or "https://api.datadoghq.com" diff --git a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py index e28f0b0b..5f7f56d2 100644 --- a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py +++ b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py @@ -35,13 +35,14 @@ class ResourceHandlerRequest(BaseResourceHandlerRequest): # pylint: disable=invalid-name desiredResourceState: Optional["ResourceModel"] previousResourceState: Optional["ResourceModel"] + typeConfiguration: Optional["TypeConfigurationModel"] @dataclass class ResourceModel(BaseModel): DatadogCredentials: Optional["_DatadogCredentials"] Creator: Optional["_Creator"] - Id: Optional[int] + Id: Optional[Any] Message: Optional[str] Name: Optional[str] Tags: Optional[Sequence[str]] @@ -231,3 +232,23 @@ def _deserialize( _MonitorThresholdWindows = MonitorThresholdWindows +@dataclass +class TypeConfigurationModel(BaseModel): + DatadogCredentials: Optional["_DatadogCredentials"] + + @classmethod + def _deserialize( + cls: Type["_TypeConfigurationModel"], + json_data: Optional[Mapping[str, Any]], + ) -> Optional["_TypeConfigurationModel"]: + if not json_data: + return None + return cls( + DatadogCredentials=DatadogCredentials._deserialize(json_data.get("DatadogCredentials")), + ) + + +# work around possible type aliasing issues when variable has same name as a model +_TypeConfigurationModel = TypeConfigurationModel + + From d0706f3cc04d52b360a6c9592060af27e4eb1022 Mon Sep 17 00:00:00 2001 From: Sherzod Karimov Date: Wed, 27 Mar 2024 15:18:16 -0400 Subject: [PATCH 2/3] update runtime --- datadog-monitors-monitor-handler/.rpdk-config | 4 ++-- .../src/datadog_monitors_monitor/models.py | 17 +++++++++-------- datadog-monitors-monitor-handler/template.yml | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/datadog-monitors-monitor-handler/.rpdk-config b/datadog-monitors-monitor-handler/.rpdk-config index e790c4ca..48440f87 100644 --- a/datadog-monitors-monitor-handler/.rpdk-config +++ b/datadog-monitors-monitor-handler/.rpdk-config @@ -1,7 +1,7 @@ { "typeName": "Datadog::Monitors::Monitor", - "language": "python37", - "runtime": "python3.7", + "language": "python39", + "runtime": "python3.9", "entrypoint": "datadog_monitors_monitor.handlers.resource", "testEntrypoint": "datadog_monitors_monitor.handlers.test_entrypoint", "settings": { diff --git a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py index 5f7f56d2..87d7445b 100644 --- a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py +++ b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/models.py @@ -1,6 +1,14 @@ # DO NOT modify this file by hand, changes will be overwritten -import sys from dataclasses import dataclass + +from cloudformation_cli_python_lib.interface import ( + BaseModel, + BaseResourceHandlerRequest, +) +from cloudformation_cli_python_lib.recast import recast_object +from cloudformation_cli_python_lib.utils import deserialize_list + +import sys from inspect import getmembers, isclass from typing import ( AbstractSet, @@ -14,13 +22,6 @@ TypeVar, ) -from cloudformation_cli_python_lib.interface import ( - BaseModel, - BaseResourceHandlerRequest, -) -from cloudformation_cli_python_lib.recast import recast_object -from cloudformation_cli_python_lib.utils import deserialize_list - T = TypeVar("T") diff --git a/datadog-monitors-monitor-handler/template.yml b/datadog-monitors-monitor-handler/template.yml index 5032a7eb..cfb43ca8 100644 --- a/datadog-monitors-monitor-handler/template.yml +++ b/datadog-monitors-monitor-handler/template.yml @@ -12,13 +12,13 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: datadog_monitors_monitor.handlers.resource - Runtime: python3.7 + Runtime: python3.9 CodeUri: build/ TestEntrypoint: Type: AWS::Serverless::Function Properties: Handler: datadog_monitors_monitor.handlers.test_entrypoint - Runtime: python3.7 + Runtime: python3.9 CodeUri: build/ From 614bcfc5719c76e41e51972e88da3286f334e3c3 Mon Sep 17 00:00:00 2001 From: Sherzod Karimov Date: Wed, 27 Mar 2024 15:21:07 -0400 Subject: [PATCH 3/3] bump version --- datadog-monitors-monitor-handler/datadog-monitors-monitor.json | 2 +- datadog-monitors-monitor-handler/docs/README.md | 2 +- .../src/datadog_monitors_monitor/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datadog-monitors-monitor-handler/datadog-monitors-monitor.json b/datadog-monitors-monitor-handler/datadog-monitors-monitor.json index d46b1123..a462ef9a 100644 --- a/datadog-monitors-monitor-handler/datadog-monitors-monitor.json +++ b/datadog-monitors-monitor-handler/datadog-monitors-monitor.json @@ -1,6 +1,6 @@ { "typeName": "Datadog::Monitors::Monitor", - "description": "Datadog Monitor 3.0.0", + "description": "Datadog Monitor 3.1.0b2", "typeConfiguration": { "properties": { "DatadogCredentials": { diff --git a/datadog-monitors-monitor-handler/docs/README.md b/datadog-monitors-monitor-handler/docs/README.md index 6b8e76a7..c84d72cb 100644 --- a/datadog-monitors-monitor-handler/docs/README.md +++ b/datadog-monitors-monitor-handler/docs/README.md @@ -1,6 +1,6 @@ # Datadog::Monitors::Monitor -Datadog Monitor 3.0.0 +Datadog Monitor 3.1.0b2 ## Syntax diff --git a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/version.py b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/version.py index 528787cf..0e915519 100644 --- a/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/version.py +++ b/datadog-monitors-monitor-handler/src/datadog_monitors_monitor/version.py @@ -1 +1 @@ -__version__ = "3.0.0" +__version__ = "3.1.0b2"