|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +# |
| 3 | +# OpenCraft -- tools to aid developing and hosting free software projects |
| 4 | +# Copyright (C) 2015-2021 OpenCraft <contact@opencraft.com> |
| 5 | +# |
| 6 | +# This program is free software: you can redistribute it and/or modify |
| 7 | +# it under the terms of the GNU Affero General Public License as |
| 8 | +# published by the Free Software Foundation, either version 3 of the |
| 9 | +# License, or (at your option) any later version. |
| 10 | +# |
| 11 | +# This program is distributed in the hope that it will be useful, |
| 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | +# GNU Affero General Public License for more details. |
| 15 | +# |
| 16 | +# You should have received a copy of the GNU Affero General Public License |
| 17 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | +""" |
| 19 | +API views for the Grove app. |
| 20 | +""" |
| 21 | + |
| 22 | +import json |
| 23 | +import re |
| 24 | +from django.http import HttpResponse |
| 25 | +from django.views.decorators.csrf import csrf_exempt |
| 26 | +from django.views.decorators.http import require_POST |
| 27 | +from rest_framework.viewsets import GenericViewSet |
| 28 | +from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, CreateModelMixin |
| 29 | +from rest_framework.permissions import IsAuthenticated |
| 30 | + |
| 31 | +from grove.models.deployment import GroveDeployment |
| 32 | +from grove.models.gitlabpipeline import GitlabPipeline |
| 33 | +from grove.serializers import GroveDeploymentSerializer |
| 34 | +from instance.models.instance import InstanceReference |
| 35 | +from opencraft.swagger import viewset_swagger_helper |
| 36 | + |
| 37 | + |
| 38 | +@viewset_swagger_helper( |
| 39 | + retrieve="Return a deployment", |
| 40 | + list="Return all deployments", |
| 41 | + create="Trigger new deployment in Grove for an existing instance", |
| 42 | + public_actions=["create"], |
| 43 | +) |
| 44 | +class GroveDeploymentAPIView(RetrieveModelMixin, ListModelMixin, CreateModelMixin, GenericViewSet): |
| 45 | + """ |
| 46 | + GroveDeploymentAPI is used to create new and monitor existing deployments. |
| 47 | +
|
| 48 | + When an instance is created by a new customer or a deployment is needed to |
| 49 | + update the existing instance configuration, a new Deployment object is |
| 50 | + created. On creation GitLab will be notified and the appropriate GitLab |
| 51 | + pipeline is called to create a new or update an existing instance. |
| 52 | + """ |
| 53 | + |
| 54 | + serializer_class = GroveDeploymentSerializer |
| 55 | + permission_classes = [IsAuthenticated] |
| 56 | + |
| 57 | + def get_queryset(self): |
| 58 | + """ |
| 59 | + Filter the queryset based on user staff state. |
| 60 | +
|
| 61 | + If the user is a staff user, return all deployments. Otherwise, return |
| 62 | + only those deployments that are related to the given user. |
| 63 | + """ |
| 64 | + |
| 65 | + user = self.request.user |
| 66 | + |
| 67 | + if user.is_staff: |
| 68 | + return GroveDeployment.objects.all() |
| 69 | + |
| 70 | + return GroveDeployment.objects.filter(instance__creator__user=user) |
| 71 | + |
| 72 | + |
| 73 | +def is_deployment_pipeline(body): |
| 74 | + """ |
| 75 | + Check if the pipeline is related to instance deployment |
| 76 | + """ |
| 77 | + return body.get('commit', '') and "deployment" in body['commit']['title'] |
| 78 | + |
| 79 | + |
| 80 | +def get_instance(body): |
| 81 | + """ |
| 82 | + Fetch instance related to deployment pipeline |
| 83 | + """ |
| 84 | + instance_name = re.findall(r"deployment\/(\w+)\/", body['commit']['title']) |
| 85 | + if not instance_name: |
| 86 | + return None |
| 87 | + |
| 88 | + return InstanceReference.objects.get(name=instance_name[0]) |
| 89 | + |
| 90 | + |
| 91 | +@require_POST |
| 92 | +@csrf_exempt |
| 93 | +def gitlab_webhook(request): |
| 94 | + """ |
| 95 | + Webhook endpoint for gitlab pipeline |
| 96 | + """ |
| 97 | + payload = json.loads(request.body.decode('utf-8')) |
| 98 | + if is_deployment_pipeline(payload): |
| 99 | + pipeline_instance = get_instance(payload) |
| 100 | + if pipeline_instance: |
| 101 | + pipeline_id = payload['object_attributes']['id'] |
| 102 | + if GitlabPipeline.objects.filter(pipeline_id=pipeline_id).exists(): |
| 103 | + # pipeline already exists, call status update method |
| 104 | + pipeline_obj = GitlabPipeline.objects.get(pipeline_id=pipeline_id) |
| 105 | + new_status = payload['object_attributes']['status'] |
| 106 | + pipeline_obj.update_status(new_status) |
| 107 | + pipeline_obj.save() |
| 108 | + else: |
| 109 | + # create new pipeline object for the deployment object |
| 110 | + deployment = pipeline_instance.instance.get_latest_deployment() |
| 111 | + new_pipeline = GitlabPipeline.objects.create(pipeline_id=pipeline_id, instance=pipeline_instance) |
| 112 | + deployment.pipeline = new_pipeline |
| 113 | + deployment.save() |
| 114 | + |
| 115 | + return HttpResponse('Webhook received', status=200) |
0 commit comments