diff --git a/docs/mint.json b/docs/mint.json index e0ddab340..b1762ba4f 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -59,6 +59,7 @@ "providers/documentation/cloudwatch-metrics", "providers/documentation/console-provider", "providers/documentation/datadog-provider", + "providers/documentation/ilert-provider", "providers/documentation/kibana-provider", "providers/documentation/discord-provider", "providers/documentation/elastic-provider", diff --git a/docs/providers/documentation/ilert-provider.mdx b/docs/providers/documentation/ilert-provider.mdx new file mode 100644 index 000000000..af1b1d310 --- /dev/null +++ b/docs/providers/documentation/ilert-provider.mdx @@ -0,0 +1,48 @@ +--- +title: "ILert" +sidebarTitle: "ILert Provider" +description: "ILert provider allows you to create, update, and resolve incidents in ILert for effective incident management and response." +--- + +## Inputs + +- `summary`: str: A brief summary of the incident or situation you're reporting. +- `status`: IlertIncidentStatus = IlertIncidentStatus.INVESTIGATING: The current status of the incident (e.g., INVESTIGATING, RESOLVED, MONITORING, IDENTIFIED). +- `message`: str = "": A detailed message describing the incident or situation. +- `affectedServices`: str = "[]": A JSON string representing the list of affected services and their statuses. +- `id`: str = "0": The ID of the incident to update. If set to "0", a new incident will be created. + +## Outputs + +_No information yet, feel free to contribute it using the "Edit this page" link at the bottom of the page_ + +## Authentication Parameters + +The `ilert_token` is required for connecting to the ILert provider. This should be a valid API token provided by ILert. + +## Connecting with the Provider + +### API Token + +To obtain the ILert API token, follow these steps: + +1. Log in to your ILert account. +2. Navigate to the "API Tokens" section under your user profile or account settings. +3. Generate a new API token. +4. Make sure "Read Permission" and "Write Permission" are checked. +5. Click on "Save" + +Ensure you have the necessary permissions assigned to the token for creating and updating incidents. + +## Scopes + +ILert integration does not require specific scopes to be set for API token as permissions are managed directly within ILert's platform. + +## Notes + +_No information yet, feel free to contribute it using the "Edit this page" link at the bottom of the page_ + +## Useful Links + +- [ILert API Documentation](https://api.ilert.com/api-docs/) +- [ILert Incident Management](https://www.ilert.com/incident-management/) diff --git a/examples/workflows/ilert-incident-upon-alert.yaml b/examples/workflows/ilert-incident-upon-alert.yaml new file mode 100644 index 000000000..09ba1dea0 --- /dev/null +++ b/examples/workflows/ilert-incident-upon-alert.yaml @@ -0,0 +1,23 @@ +id: aad72d69-92b9-4e21-8f67-97d2a69bf8ac +description: Create ILert incident upon Keep Alert +triggers: +- filters: + - key: source + value: keep + type: alert +owners: [] +services: [] +steps: [] +actions: +- name: ilert-action + provider: + config: '{{ providers.ilert-default }}' + type: ilert + with: + affectedServices: + - impact: OPERATIONAL + service: + id: 339743 + message: A mock incident created with Keep! + status: INVESTIGATING + summary: Keep Incident {{ alert.name }} diff --git a/keep-ui/public/icons/ilert-icon.png b/keep-ui/public/icons/ilert-icon.png new file mode 100644 index 000000000..a6ad38087 Binary files /dev/null and b/keep-ui/public/icons/ilert-icon.png differ diff --git a/keep/providers/ilert_provider/__init__.py b/keep/providers/ilert_provider/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/keep/providers/ilert_provider/ilert_provider.py b/keep/providers/ilert_provider/ilert_provider.py new file mode 100644 index 000000000..787ef9597 --- /dev/null +++ b/keep/providers/ilert_provider/ilert_provider.py @@ -0,0 +1,246 @@ +""" +Ilert Provider is a class that allows to create/close incidents in Ilert. +""" +import dataclasses +import enum +import json +import os + +import pydantic +import requests + +from keep.contextmanager.contextmanager import ContextManager +from keep.providers.base.base_provider import BaseProvider +from keep.providers.models.provider_config import ProviderConfig, ProviderScope +from keep.providers.providers_factory import ProvidersFactory + + +class IlertIncidentStatus(str, enum.Enum): + """ + Ilert incident status. + """ + + INVESTIGATING = "INVESTIGATING" + RESOLVED = "RESOLVED" + MONITORING = "MONITORING" + IDENTIFIED = "IDENTIFIED" + + +class IlertServiceStatus(str, enum.Enum): + """ + Ilert service status. + """ + + OPERATIONAL = "OPERATIONAL" + DEGRADED = "DEGRADED" + PARTIAL_OUTAGE = "PARTIAL_OUTAGE" + MAJOR_OUTAGE = "MAJOR_OUTAGE" + UNDER_MAINTENANCE = "UNDER_MAINTENANCE" + + +class IlertServiceNoIncludes(pydantic.BaseModel): + """ + Ilert service. + """ + + id: str + + +class IlertAffectedService(pydantic.BaseModel): + """ + Ilert affected service. + """ + + service: IlertServiceNoIncludes + impact: IlertServiceStatus + + +@pydantic.dataclasses.dataclass +class IlertProviderAuthConfig: + """ + Ilert authentication configuration. + """ + + ilert_token: str = dataclasses.field( + metadata={ + "required": True, + "description": "ILert API token", + "hint": "Bearer eyJhbGc...", + "sensitive": True, + } + ) + ilert_host: str = dataclasses.field( + metadata={ + "required": False, + "description": "ILert API host", + "hint": "https://api.ilert.com/api", + }, + default="https://api.ilert.com/api", + ) + + +class IlertProvider(BaseProvider): + """Create/Resolve incidents in Ilert.""" + + PROVIDER_SCOPES = [ + ProviderScope("read_permission", "Read permission", mandatory=True), + ProviderScope("write_permission", "Write permission", mandatory=False), + ] + + def __init__( + self, context_manager: ContextManager, provider_id: str, config: ProviderConfig + ): + super().__init__(context_manager, provider_id, config) + + def dispose(self): + """ + Dispose the provider. + """ + pass + + def validate_config(self): + """ + Validates required configuration for Ilert provider. + + """ + self.authentication_config = IlertProviderAuthConfig( + **self.config.authentication + ) + + def validate_scopes(self): + scopes = {} + self.logger.info("Validating scopes") + for scope in self.PROVIDER_SCOPES: + try: + if scope.name == "read_permission": + requests.get( + f"{self.authentication_config.ilert_host}/incidents", + headers={ + "Authorization": self.authentication_config.ilert_token + }, + ) + scopes[scope.name] = True + elif scope.name == "write_permission": + # TODO: find a way to validate write_permissions, for now it is always "validated" sucessfully. + scopes[scope.name] = True + except Exception as e: + self.logger.warning( + "Failed to validate scope", + extra={"scope": scope.name}, + ) + scopes[scope.name] = str(e) + self.logger.info("Scopes validated", extra=scopes) + return scopes + + def _notify( + self, + summary: str, + status: IlertIncidentStatus = IlertIncidentStatus.INVESTIGATING, + message: str = "", + affectedServices: str | list = "[]", + id: str = "0", + **kwargs: dict, + ): + self.logger.info( + "Creating/updating Ilert incident", + extra={ + "summary": summary, + "status": status, + "incident_message": message, + "affectedServices": affectedServices, + "id": id, + }, + ) + headers = {"Authorization": self.authentication_config.ilert_token} + + # Create or update incident + payload = { + "id": id, + "summary": summary, + "status": str(status), + "message": message, + **kwargs, + } + if affectedServices: + try: + payload["affectedServices"] = ( + json.loads(affectedServices) + if isinstance(affectedServices, str) + else affectedServices + ) + except Exception: + self.logger.warning( + "Failed to parse affectedServices", + extra={"affectedServices": affectedServices}, + ) + + # if id is set, we update the incident, otherwise we create a new one + should_update = id and id != "0" + if not should_update: + response = requests.post( + f"{self.authentication_config.ilert_host}/incidents", + headers=headers, + json=payload, + ) + else: + response = requests.put( + f"{self.authentication_config.ilert_host}/incidents/{id}", + headers=headers, + json=payload, + ) + + if not response.ok: + self.logger.error( + "Failed to create/update Ilert incident", + extra={ + "status_code": response.status_code, + "response": response.text, + }, + ) + raise Exception( + f"Failed to create/update Ilert incident: {response.status_code} {response.text}" + ) + self.logger.info( + "Ilert incident created/updated", + extra={"status_code": response.status_code}, + ) + + +if __name__ == "__main__": + # Output debug messages + import logging + + logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()]) + context_manager = ContextManager( + tenant_id="singletenant", + workflow_id="test", + ) + # Load environment variables + import os + + api_key = os.environ.get("ILERT_API_TOKEN") + + provider_config = { + "authentication": {"ilert_token": api_key}, + } + provider: IlertProvider = ProvidersFactory.get_provider( + context_manager=context_manager, + provider_id="ilert", + provider_type="ilert", + provider_config=provider_config, + ) + result = provider._query( + "Example", + message="Lorem Ipsum", + status="MONITORING", + affectedServices=json.dumps( + [ + { + "impact": "OPERATIONAL", + "service": {"id": 339743}, + } + ] + ), + id="242530", + ) + print(result)