Skip to content

Commit

Permalink
alert plugin implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed May 11, 2024
1 parent 3066006 commit d171195
Show file tree
Hide file tree
Showing 13 changed files with 290 additions and 42 deletions.
100 changes: 100 additions & 0 deletions docs/source/usage/alerts.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.. _usage_alerts:

.. include:: ../_include/head.rst

.. include:: ../_include/warn_develop.rst


======
Alerts
======

You can use the UI at :code:`Settings - Alerts` to create alerting rules for your jobs.

Options are:

* **User** specific rule - only you are notified

* **Group** rules - all members of a specific group are notified (*if they have the privilege to view the job*)

* **Global** rules - all users are notified (*if they have the privilege to view the job*)

----

Media
*****

There are currently two types of alerts.

E-Mail and plugins.

----

E-Mail
======

You need to configure your mailserver at the :code:`System - Config` page.

After that you can receive e-mails on job finish/failure.

----

Plugins
=======

There is a generic alert-plugin interface for custom solutions.

**Usage**:

* Create a script that can be called by AW

It will receive a file-path as system-argument 1 that points to a JSON file containing data you might want to use.

Example JSON:

.. code-block:: json
{
"alert": {
"type": "user",
"condition": "always"
},
"user": {
"name": "ansible",
"first_name": "",
"last_name": "",
"email": "ansible@localhost",
"phone": "",
"description": "",
"is_active": true,
"last_login": 1715445321,
"groups": []
},
"execution": {
"failed": false,
"status": "Finished",
"job_name": "test2",
"user_name": "ansible",
"time_start": 1715450650,
"time_start_pretty": "2024-05-11 18:04:10 CEST",
"time_fin": 1715450651,
"time_fin_pretty": "2024-05-11 18:04:11 CEST",
"error_short": null,
"error_med": null,
"log_stdout": "/home/guy/.local/share/ansible-webui/test2_2024-05-11_18-04-10_ansible_stdout.log",
"log_stdout_url": "http://localhost:8000/api/job/1/17/log?type=stdout",
"log_stderr": null,
"log_stderr_url": null,
"log_stdout_repo": null,
"log_stdout_repo_url": null,
"log_stderr_repo": null,
"log_stderr_repo_url": null
},
"stats": {}
}
* Create a plugin at :code:`Settings - Alerts` that points to your executable

* Link the plugin in alerts

* Test it
22 changes: 22 additions & 0 deletions src/ansibleguy-webui/aw/admin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from rest_framework_api_key.admin import APIKey

from aw.model.api import AwAPIKey
from aw.model.job import Job, JobExecution, JobExecutionResult, JobError, JobExecutionResultHost
from aw.model.permission import JobPermission, JobPermissionMemberUser, JobPermissionMemberGroup, \
JobPermissionMapping
from aw.model.job_credential import JobGlobalCredentials, JobUserCredentials
from aw.model.repository import Repository
from aw.model.system import SystemConfig, UserExtended
from aw.model.alert import AlertUser, AlertGroup, AlertGlobal, AlertPlugin


class UserExtendedInline(admin.StackedInline):
model = UserExtended
can_delete = False


class UserAdmin(BaseUserAdmin):
inlines = [UserExtendedInline]


admin.site.unregister(APIKey)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

admin.site.register(Job)
admin.site.register(JobExecution)
Expand All @@ -22,3 +38,9 @@
admin.site.register(JobGlobalCredentials)
admin.site.register(JobUserCredentials)
admin.site.register(AwAPIKey)
admin.site.register(Repository)
admin.site.register(SystemConfig)
admin.site.register(AlertUser)
admin.site.register(AlertGroup)
admin.site.register(AlertGlobal)
admin.site.register(AlertPlugin)
51 changes: 21 additions & 30 deletions src/ansibleguy-webui/aw/api_endpoints/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,6 @@
from aw.model.alert import AlertPlugin, AlertGlobal, AlertGroup, AlertUser


class BaseAlertWriteRequest(BaseResponse):
# NOTE: not using modelserializer because issues with DRF and PUT unique constraints
name = serializers.CharField(required=True)
alert_type = serializers.IntegerField(required=False)
condition = serializers.IntegerField(required=False)
jobs = serializers.ListSerializer(child=serializers.IntegerField(), required=False)
jobs_all = serializers.BooleanField()
# todo: require alert to be provided if alert-type is plugin
plugin = serializers.IntegerField(required=False)


class AlertPluginReadWrite(serializers.ModelSerializer):
class Meta:
model = AlertPlugin
Expand Down Expand Up @@ -181,11 +170,13 @@ class Meta:
condition_name = serializers.CharField()


class AlertUserCreateRequest(serializers.ModelSerializer):
class AlertUserWriteRequest(serializers.ModelSerializer):
class Meta:
model = AlertUser
fields = AlertUser.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


class APIAlertUser(GenericAPIView):
http_method_names = ['get', 'post']
Expand All @@ -206,14 +197,14 @@ def get(request):
)

@extend_schema(
request=AlertUserCreateRequest,
request=AlertUserWriteRequest,
responses=api_docs_post('Alert'),
summary='Create a new Alert.',
operation_id='alert_user_create',
)
def post(self, request):
user = get_api_user(request)
serializer = AlertUserCreateRequest(data=request.data)
serializer = AlertUserWriteRequest(data=request.data)

if not serializer.is_valid():
return Response(
Expand Down Expand Up @@ -266,14 +257,14 @@ def get(request, alert_id: int):
)

@extend_schema(
request=BaseAlertWriteRequest,
request=AlertUserWriteRequest,
responses=api_docs_put('Alert'),
summary='Modify an Alert.',
operation_id='alert_user_edit',
)
def put(self, request, alert_id: int):
user = get_api_user(request)
serializer = BaseAlertWriteRequest(data=request.data)
serializer = AlertUserWriteRequest(data=request.data)

if not serializer.is_valid():
return Response(
Expand Down Expand Up @@ -335,11 +326,13 @@ class Meta:
condition_name = serializers.CharField()


class AlertGlobalCreateRequest(serializers.ModelSerializer):
class AlertGlobalWriteRequest(serializers.ModelSerializer):
class Meta:
model = AlertGlobal
fields = AlertGlobal.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


class APIAlertGlobal(GenericAPIView):
http_method_names = ['get', 'post']
Expand All @@ -358,7 +351,7 @@ def get(request):
return Response([AlertGlobalReadResponse(instance=alert).data for alert in AlertGlobal.objects.all()])

@extend_schema(
request=AlertGlobalCreateRequest,
request=AlertGlobalWriteRequest,
responses=api_docs_post('Alert'),
summary='Create a new Alert.',
operation_id='alert_global_create',
Expand All @@ -371,7 +364,7 @@ def post(self, request):
status=403,
)

serializer = AlertGlobalCreateRequest(data=request.data)
serializer = AlertGlobalWriteRequest(data=request.data)

if not serializer.is_valid():
return Response(
Expand Down Expand Up @@ -419,7 +412,7 @@ def get(request, alert_id: int):
return Response(data={'msg': f"Alert with ID {alert_id} does not exist"}, status=404)

@extend_schema(
request=BaseAlertWriteRequest,
request=AlertGlobalWriteRequest,
responses=api_docs_put('Alert'),
summary='Modify an Alert.',
operation_id='alert_global_edit',
Expand All @@ -432,7 +425,7 @@ def put(self, request, alert_id: int):
status=403,
)

serializer = BaseAlertWriteRequest(data=request.data)
serializer = AlertGlobalWriteRequest(data=request.data)

if not serializer.is_valid():
return Response(
Expand Down Expand Up @@ -495,15 +488,13 @@ class Meta:
group_name = serializers.CharField()


class AlertGrouUpdateRequest(BaseAlertWriteRequest):
group = serializers.IntegerField(required=True)


class AlertGroupCreateRequest(serializers.ModelSerializer):
class AlertGroupWriteRequest(serializers.ModelSerializer):
class Meta:
model = AlertGroup
fields = AlertGroup.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


class APIAlertGroup(GenericAPIView):
http_method_names = ['get', 'post']
Expand All @@ -524,7 +515,7 @@ def get(request):
)

@extend_schema(
request=AlertGroupCreateRequest,
request=AlertGroupWriteRequest,
responses=api_docs_post('Alert'),
summary='Create a new Alert.',
operation_id='alert_group_create',
Expand All @@ -537,7 +528,7 @@ def post(self, request):
status=403,
)

serializer = AlertGroupCreateRequest(data=request.data)
serializer = AlertGroupWriteRequest(data=request.data)

if not serializer.is_valid():
return Response(
Expand Down Expand Up @@ -589,7 +580,7 @@ def get(request, alert_id: int):
)

@extend_schema(
request=AlertGrouUpdateRequest,
request=AlertGroupWriteRequest,
responses=api_docs_put('Alert'),
summary='Modify an Alert.',
operation_id='alert_group_edit',
Expand All @@ -602,7 +593,7 @@ def put(self, request, alert_id: int):
status=403,
)

serializer = AlertGrouUpdateRequest(data=request.data)
serializer = AlertGroupWriteRequest(data=request.data)

if not serializer.is_valid():
return Response(
Expand Down
2 changes: 2 additions & 0 deletions src/ansibleguy-webui/aw/api_endpoints/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class Meta:
model = Job
fields = Job.api_fields_write

name = serializers.CharField(validators=[]) # uc on update


def _find_job(job_id: int) -> (Job, None):
try:
Expand Down
8 changes: 7 additions & 1 deletion src/ansibleguy-webui/aw/execute/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ def _condition_filter(self, alerts: list[BaseAlert]):

def _route(self, alert: BaseAlert, user: USERS):
if alert.alert_type == ALERT_TYPE_PLUGIN:
alert_plugin_wrapper(alert=alert, user=user, stats=self.stats, execution=self.execution)
alert_plugin_wrapper(
alert=alert,
user=user,
stats=self.stats,
execution=self.execution,
failed=self.failed,
)

else:
alert_plugin_email(user=user, stats=self.stats, execution=self.execution)
Expand Down
4 changes: 2 additions & 2 deletions src/ansibleguy-webui/aw/execute/alert_plugin/plugin_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from aw.model.system import MAIL_TRANSPORT_TYPE_SSL, MAIL_TRANSPORT_TYPE_STARTTLS


def _email_send(server: SMTP, user: USERS, stats: list[dict], execution: JobExecution):
def _email_send(server: SMTP, user: USERS, stats: dict, execution: JobExecution):
server.login(user=config['mail_user'], password=config['mail_pass'])
msg = MIMEMultipart('alternative')
msg['Subject'] = f"Ansible WebUI Alert - Job '{execution.job.name}' - {execution.status_name}"
Expand Down Expand Up @@ -65,7 +65,7 @@ def _email_send(server: SMTP, user: USERS, stats: list[dict], execution: JobExec
)


def alert_plugin_email(user: USERS, stats: list[dict], execution: JobExecution):
def alert_plugin_email(user: USERS, stats: dict, execution: JobExecution):
if user.email.endswith('@localhost') or not valid_email(user.email):
log(msg=f"User has an invalid email address configured: {user.username} ({user.email})", level=3)
return
Expand Down
Loading

0 comments on commit d171195

Please sign in to comment.