Skip to content

Commit

Permalink
feat: activations with webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
mkanoor committed Jan 11, 2024
1 parent 35a741f commit e673bba
Show file tree
Hide file tree
Showing 24 changed files with 2,021 additions and 5 deletions.
144 changes: 142 additions & 2 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ rq-scheduler = "^0.10"
# other AAP components
django-ansible-base = { git = "https://github.com/ansible/django-ansible-base.git", branch="devel", extras=["authentication"] }
jinja2 = "*"
psycopg = "^3.1.17"
xxhash = "^3.4.1"

[tool.poetry.group.test.dependencies]
pytest = "*"
Expand Down
29 changes: 29 additions & 0 deletions src/aap_eda/api/filters/webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2024 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import django_filters

from aap_eda.core import models


class WebhookFilter(django_filters.FilterSet):
name = django_filters.CharFilter(
field_name="name",
lookup_expr="istartswith",
label="Filter by webhook name.",
)

class Meta:
model = models.Webhook
fields = ["name"]
4 changes: 4 additions & 0 deletions src/aap_eda/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
UserListSerializer,
UserSerializer,
)
from .webhook import WebhookInSerializer, WebhookOutSerializer

__all__ = (
# auth
Expand Down Expand Up @@ -122,4 +123,7 @@
"RoleSerializer",
"RoleListSerializer",
"RoleDetailSerializer",
# webhooks
"WebhookInSerializer",
"WebhookOutSerializer",
)
33 changes: 32 additions & 1 deletion src/aap_eda/api/serializers/activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@
ProjectRefSerializer,
)
from aap_eda.api.serializers.rulebook import RulebookRefSerializer
from aap_eda.api.serializers.webhook import WebhookOutSerializer
from aap_eda.core import models, validators


class ActivationSerializer(serializers.ModelSerializer):
"""Serializer for the Activation model."""

webhooks = serializers.ListField(
required=False,
allow_null=True,
child=WebhookOutSerializer(),
)

class Meta:
model = models.Activation
fields = [
Expand All @@ -49,6 +56,7 @@ class Meta:
"modified_at",
"status_message",
"awx_token_id",
"webhooks",
]
read_only_fields = [
"id",
Expand All @@ -63,6 +71,11 @@ class ActivationListSerializer(serializers.ModelSerializer):

rules_count = serializers.IntegerField()
rules_fired_count = serializers.IntegerField()
webhooks = serializers.ListField(
required=False,
allow_null=True,
child=WebhookOutSerializer(),
)

class Meta:
model = models.Activation
Expand All @@ -86,14 +99,18 @@ class Meta:
"modified_at",
"status_message",
"awx_token_id",
"webhooks",
]
read_only_fields = ["id", "created_at", "modified_at"]

def to_representation(self, activation):
rules_count, rules_fired_count = get_rules_count(
activation.ruleset_stats
)

webhooks = [
WebhookOutSerializer(webhook).data
for webhook in activation.webhooks.all()
]
return {
"id": activation.id,
"name": activation.name,
Expand All @@ -114,6 +131,7 @@ def to_representation(self, activation):
"modified_at": activation.modified_at,
"status_message": activation.status_message,
"awx_token_id": activation.awx_token_id,
"webhooks": webhooks,
}


Expand All @@ -132,6 +150,7 @@ class Meta:
"user",
"restart_policy",
"awx_token_id",
"webhooks",
]

rulebook_id = serializers.IntegerField(
Expand All @@ -152,6 +171,12 @@ class Meta:
validators=[validators.check_if_awx_token_exists],
required=False,
)
webhooks = serializers.ListField(
required=False,
allow_null=True,
child=serializers.IntegerField(),
validators=[validators.check_if_webhooks_exists],
)

def validate(self, data):
user = data["user"]
Expand Down Expand Up @@ -244,6 +269,7 @@ class Meta:
"restarted_at",
"status_message",
"awx_token_id",
"webhooks",
]
read_only_fields = ["id", "created_at", "modified_at", "restarted_at"]

Expand Down Expand Up @@ -284,6 +310,10 @@ def to_representation(self, activation):
if len(activation_instances) > 1 and activation.restart_count > 0
else None
)
webhooks = [
WebhookOutSerializer(webhook).data
for webhook in activation.webhooks.all()
]

return {
"id": activation.id,
Expand All @@ -310,6 +340,7 @@ def to_representation(self, activation):
"restarted_at": restarted_at,
"status_message": activation.status_message,
"awx_token_id": activation.awx_token_id,
"webhooks": webhooks,
}


Expand Down
87 changes: 87 additions & 0 deletions src/aap_eda/api/serializers/webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright 2024 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from rest_framework import serializers

from aap_eda.core import models, validators


class WebhookInSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
hmac_algorithm = serializers.CharField(
default="sha256",
help_text="Hash algorithm to use",
validators=[validators.valid_hash_algorithm],
)
hmac_format = serializers.CharField(
default="hex",
help_text="Hash format to use, hex or base64",
validators=[validators.valid_hash_format],
)
auth_type = serializers.CharField(
default="hmac",
help_text="Auth type to use hmac or token",
validators=[validators.valid_webhook_auth_type],
)
additional_data_headers = serializers.ListField(
required=False,
allow_null=True,
child=serializers.CharField(),
)

class Meta:
model = models.Webhook
fields = [
"name",
"hmac_algorithm",
"header_key",
"auth_type",
"hmac_signature_prefix",
"hmac_format",
"user",
"secret",
"test_mode",
"additional_data_headers",
]


class WebhookOutSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()

class Meta:
model = models.Webhook
read_only_fields = [
"id",
"url",
"created_at",
"modified_at",
"test_content_type",
"test_content",
"test_error_message",
]
fields = [
"name",
"test_mode",
"user",
"hmac_algorithm",
"header_key",
"hmac_signature_prefix",
"hmac_format",
"auth_type",
"additional_data_headers",
*read_only_fields,
]

def get_user(self, obj) -> str:
return f"{obj.user.username}"
5 changes: 5 additions & 0 deletions src/aap_eda/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@
router.register("activation-instances", views.ActivationInstanceViewSet)
router.register("audit-rules", views.AuditRuleViewSet)
router.register("users", views.UserViewSet)
router.register("webhooks", views.WebhookViewSet)
router.register(
"users/me/awx-tokens",
views.CurrentUserAwxTokenViewSet,
basename="controller-token",
)
router.register(
"external_webhook",
views.ExternalWebhookViewSet,
)
router.register("credentials", views.CredentialViewSet)
router.register("decision-environments", views.DecisionEnvironmentViewSet)

Expand Down
6 changes: 6 additions & 0 deletions src/aap_eda/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .auth import RoleViewSet, SessionLoginView, SessionLogoutView
from .credential import CredentialViewSet
from .decision_environment import DecisionEnvironmentViewSet
from .external_webhook import ExternalWebhookViewSet
from .project import ExtraVarViewSet, ProjectViewSet
from .rulebook import (
AuditRuleViewSet,
Expand All @@ -25,6 +26,7 @@
)
from .tasks import TaskViewSet
from .user import CurrentUserAwxTokenViewSet, CurrentUserView, UserViewSet
from .webhook import WebhookViewSet

__all__ = (
# auth
Expand All @@ -51,4 +53,8 @@
"CredentialViewSet",
# decision_environment
"DecisionEnvironmentViewSet",
# webhook
"WebhookViewSet",
# External webhook
"ExternalWebhookViewSet",
)
24 changes: 24 additions & 0 deletions src/aap_eda/api/views/activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.
import logging

import yaml
from django.conf import settings
from django.shortcuts import get_object_or_404
from django_filters import rest_framework as defaultfilters
from drf_spectacular.utils import (
Expand Down Expand Up @@ -80,6 +82,28 @@ def create(self, request):

response = serializer.create(serializer.validated_data)

if response.webhooks:
extra_var = {}
if response.extra_var_id:
extra_var_obj = models.ExtraVar.objects.get(
pk=response.extra_var_id
)
extra_var = yaml.safe_load(extra_var_obj.extra_var)

extra_var["EDA_WEBHOOK_CHANNELS"] = [
webhook.channel_name for webhook in response.webhooks.all()
]
extra_var["EDA_PG_NOTIFY_DSN"] = settings.PG_NOTIFY_DSN_CLIENT
if response.extra_var_id and extra_var_obj:
extra_var_obj.extra_var = yaml.dump(extra_var)
extra_var_obj.save()
else:
extra_var_obj = models.ExtraVar.objects.create(
extra_var=yaml.dump(extra_var)
)
response.extra_var_id = extra_var_obj.id
response.save(update_fields=["extra_var_id"])

if response.is_enabled:
start_activation(activation_id=response.id)

Expand Down

0 comments on commit e673bba

Please sign in to comment.