diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 48efd6a204..07c9b3aaf2 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -4917,6 +4917,7 @@ class InstanceSerializer(BaseSerializer): res = super(InstanceSerializer, self).get_related(obj) res['jobs'] = self.reverse('api:instance_unified_jobs_list', kwargs={'pk': obj.pk}) res['instance_groups'] = self.reverse('api:instance_instance_groups_list', kwargs={'pk': obj.pk}) + res['install_bundle'] = self.reverse('api:instance_install_bundle', kwargs={'pk': obj.pk}) if self.context['request'].user.is_superuser or self.context['request'].user.is_system_auditor: if obj.node_type != 'hop': res['health_check'] = self.reverse('api:instance_health_check', kwargs={'pk': obj.pk}) diff --git a/awx/api/urls/instance.py b/awx/api/urls/instance.py index 6c70e285c5..a9ef203384 100644 --- a/awx/api/urls/instance.py +++ b/awx/api/urls/instance.py @@ -3,7 +3,7 @@ from django.urls import re_path -from awx.api.views import InstanceList, InstanceDetail, InstanceUnifiedJobsList, InstanceInstanceGroupsList, InstanceHealthCheck +from awx.api.views import InstanceList, InstanceDetail, InstanceUnifiedJobsList, InstanceInstanceGroupsList, InstanceHealthCheck, InstanceInstallBundle urls = [ @@ -12,6 +12,7 @@ urls = [ re_path(r'^(?P[0-9]+)/jobs/$', InstanceUnifiedJobsList.as_view(), name='instance_unified_jobs_list'), re_path(r'^(?P[0-9]+)/instance_groups/$', InstanceInstanceGroupsList.as_view(), name='instance_instance_groups_list'), re_path(r'^(?P[0-9]+)/health_check/$', InstanceHealthCheck.as_view(), name='instance_health_check'), + re_path(r'^(?P[0-9]+)/install_bundle/$', InstanceInstallBundle.as_view(), name='instance_install_bundle'), ] __all__ = ['urls'] diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 3c282f3e2c..9991bcfe1c 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -174,6 +174,7 @@ from awx.api.views.webhooks import WebhookKeyView, GithubWebhookReceiver, Gitlab from awx.api.pagination import UnifiedJobEventPagination from awx.main.utils import set_environ +from awx.api.views.instance_install_bundle import InstanceInstallBundle # noqa logger = logging.getLogger('awx.api.views') diff --git a/awx/api/views/instance_install_bundle.py b/awx/api/views/instance_install_bundle.py new file mode 100644 index 0000000000..f3f017d146 --- /dev/null +++ b/awx/api/views/instance_install_bundle.py @@ -0,0 +1,54 @@ +# Copyright (c) 2018 Red Hat, Inc. +# All Rights Reserved. + +import os +import tarfile +import tempfile + +from awx.api import serializers +from awx.api.generics import GenericAPIView, Response +from awx.api.permissions import IsSystemAdminOrAuditor +from awx.main import models +from django.utils.translation import gettext_lazy as _ +from rest_framework import status +from django.http import HttpResponse + +# generate install bundle for the instance +class InstanceInstallBundle(GenericAPIView): + + name = _('Install Bundle') + model = models.Instance + serializer_class = serializers.InstanceSerializer + permission_classes = (IsSystemAdminOrAuditor,) + + def get(self, request, *args, **kwargs): + instance_obj = self.get_object() + + # if the instance is not a hop or execution node than return 400 + if instance_obj.node_type not in ('execution', 'hop'): + return Response( + data=dict(msg=_('Install bundle can only be generated for execution or hop nodes.')), + status=status.HTTP_400_BAD_REQUEST, + ) + + # TODO: add actual data into the bundle + # create a named temporary file directory to store the content of the install bundle + with tempfile.TemporaryDirectory() as tmpdirname: + # create a empty file named "moc_content.txt" in the temporary directory + with open(os.path.join(tmpdirname, 'mock_content.txt'), 'w') as f: + f.write('mock content') + + # create empty directory in temporary directory + os.mkdir(os.path.join(tmpdirname, 'mock_dir')) + + # tar.gz and create a temporary file from the temporary directory + # the directory will be renamed and prefixed with the hostname of the instance + with tempfile.NamedTemporaryFile(suffix='.tar.gz') as tmpfile: + with tarfile.open(tmpfile.name, 'w:gz') as tar: + tar.add(tmpdirname, arcname=f"{instance_obj.hostname}_install_bundle") + + # read the temporary file and send it to the client + with open(tmpfile.name, 'rb') as f: + response = HttpResponse(f.read(), status=status.HTTP_200_OK) + response['Content-Disposition'] = f"attachment; filename={instance_obj.hostname}_install_bundle.tar.gz" + return response