mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02:30
Add ReceptorAddress to root urls
- Add database contraints to make sure addresses are unique If port is defined: address, port, protocol, websocket_path are unique together if port is not defined: address, protocol, websocket_path are unique together - Allow deleting address via API - Add ReceptorAddressAccess to determine permissions - awx-manage add_receptor_address returns changed: True if successful
This commit is contained in:
@@ -5482,7 +5482,7 @@ class ReceptorAddressSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ReceptorAddress
|
model = ReceptorAddress
|
||||||
fields = ('id', 'address', 'port', 'protocol', 'websocket_path', 'is_internal', 'instance', 'full_address')
|
fields = ('id', 'url', 'address', 'port', 'protocol', 'websocket_path', 'is_internal', 'instance', 'full_address')
|
||||||
read_only = 'full_address'
|
read_only = 'full_address'
|
||||||
|
|
||||||
def get_full_address(self, obj):
|
def get_full_address(self, obj):
|
||||||
@@ -5557,7 +5557,7 @@ class InstanceSerializer(BaseSerializer):
|
|||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(InstanceSerializer, self).get_related(obj)
|
res = super(InstanceSerializer, self).get_related(obj)
|
||||||
res['receptor_addresses'] = self.reverse('api:receptor_addresses_list', kwargs={'pk': obj.pk})
|
res['receptor_addresses'] = self.reverse('api:instance_receptor_addresses_list', kwargs={'pk': obj.pk})
|
||||||
res['jobs'] = self.reverse('api:instance_unified_jobs_list', kwargs={'pk': obj.pk})
|
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['instance_groups'] = self.reverse('api:instance_instance_groups_list', kwargs={'pk': obj.pk})
|
||||||
if obj.node_type in [Instance.Types.EXECUTION, Instance.Types.HOP]:
|
if obj.node_type in [Instance.Types.EXECUTION, Instance.Types.HOP]:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from awx.api.views import (
|
|||||||
InstanceInstanceGroupsList,
|
InstanceInstanceGroupsList,
|
||||||
InstanceHealthCheck,
|
InstanceHealthCheck,
|
||||||
InstancePeersList,
|
InstancePeersList,
|
||||||
ReceptorAddressesList,
|
InstanceReceptorAddressesList,
|
||||||
)
|
)
|
||||||
from awx.api.views.instance_install_bundle import InstanceInstallBundle
|
from awx.api.views.instance_install_bundle import InstanceInstallBundle
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ urls = [
|
|||||||
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', InstanceInstanceGroupsList.as_view(), name='instance_instance_groups_list'),
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', InstanceInstanceGroupsList.as_view(), name='instance_instance_groups_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/health_check/$', InstanceHealthCheck.as_view(), name='instance_health_check'),
|
re_path(r'^(?P<pk>[0-9]+)/health_check/$', InstanceHealthCheck.as_view(), name='instance_health_check'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/peers/$', InstancePeersList.as_view(), name='instance_peers_list'),
|
re_path(r'^(?P<pk>[0-9]+)/peers/$', InstancePeersList.as_view(), name='instance_peers_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/receptor_addresses/$', ReceptorAddressesList.as_view(), name='receptor_addresses_list'),
|
re_path(r'^(?P<pk>[0-9]+)/receptor_addresses/$', InstanceReceptorAddressesList.as_view(), name='instance_receptor_addresses_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/install_bundle/$', InstanceInstallBundle.as_view(), name='instance_install_bundle'),
|
re_path(r'^(?P<pk>[0-9]+)/install_bundle/$', InstanceInstallBundle.as_view(), name='instance_install_bundle'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
17
awx/api/urls/receptor_address.py
Normal file
17
awx/api/urls/receptor_address.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Copyright (c) 2017 Ansible, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from awx.api.views import (
|
||||||
|
ReceptorAddressesList,
|
||||||
|
ReceptorAddressDetail,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
urls = [
|
||||||
|
re_path(r'^$', ReceptorAddressesList.as_view(), name='receptor_addresses_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/$', ReceptorAddressDetail.as_view(), name='receptor_address_detail'),
|
||||||
|
]
|
||||||
|
|
||||||
|
__all__ = ['urls']
|
||||||
@@ -85,6 +85,7 @@ from .oauth2_root import urls as oauth2_root_urls
|
|||||||
from .workflow_approval_template import urls as workflow_approval_template_urls
|
from .workflow_approval_template import urls as workflow_approval_template_urls
|
||||||
from .workflow_approval import urls as workflow_approval_urls
|
from .workflow_approval import urls as workflow_approval_urls
|
||||||
from .analytics import urls as analytics_urls
|
from .analytics import urls as analytics_urls
|
||||||
|
from .receptor_address import urls as receptor_address_urls
|
||||||
|
|
||||||
v2_urls = [
|
v2_urls = [
|
||||||
re_path(r'^$', ApiV2RootView.as_view(), name='api_v2_root_view'),
|
re_path(r'^$', ApiV2RootView.as_view(), name='api_v2_root_view'),
|
||||||
@@ -155,6 +156,7 @@ v2_urls = [
|
|||||||
re_path(r'^bulk/host_create/$', BulkHostCreateView.as_view(), name='bulk_host_create'),
|
re_path(r'^bulk/host_create/$', BulkHostCreateView.as_view(), name='bulk_host_create'),
|
||||||
re_path(r'^bulk/host_delete/$', BulkHostDeleteView.as_view(), name='bulk_host_delete'),
|
re_path(r'^bulk/host_delete/$', BulkHostDeleteView.as_view(), name='bulk_host_delete'),
|
||||||
re_path(r'^bulk/job_launch/$', BulkJobLaunchView.as_view(), name='bulk_job_launch'),
|
re_path(r'^bulk/job_launch/$', BulkJobLaunchView.as_view(), name='bulk_job_launch'),
|
||||||
|
re_path(r'^receptor_addresses/', include(receptor_address_urls)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ class InstancePeersList(SubListAPIView):
|
|||||||
relationship = 'peers'
|
relationship = 'peers'
|
||||||
|
|
||||||
|
|
||||||
class ReceptorAddressesList(ListCreateAPIView):
|
class InstanceReceptorAddressesList(ListCreateAPIView):
|
||||||
name = _("Receptor Addresses")
|
name = _("Receptor Addresses")
|
||||||
model = models.ReceptorAddress
|
model = models.ReceptorAddress
|
||||||
serializer_class = serializers.ReceptorAddressSerializer
|
serializer_class = serializers.ReceptorAddressSerializer
|
||||||
@@ -397,6 +397,18 @@ class ReceptorAddressesList(ListCreateAPIView):
|
|||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ReceptorAddressesList(ListAPIView):
|
||||||
|
name = _("Receptor Addresses")
|
||||||
|
model = models.ReceptorAddress
|
||||||
|
serializer_class = serializers.ReceptorAddressSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ReceptorAddressDetail(RetrieveUpdateDestroyAPIView):
|
||||||
|
name = _("Receptor Address Detail")
|
||||||
|
model = models.ReceptorAddress
|
||||||
|
serializer_class = serializers.ReceptorAddressSerializer
|
||||||
|
|
||||||
|
|
||||||
class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAttachDetachAPIView):
|
class InstanceInstanceGroupsList(InstanceGroupMembershipMixin, SubListCreateAttachDetachAPIView):
|
||||||
name = _("Instance's Instance Groups")
|
name = _("Instance's Instance Groups")
|
||||||
model = models.InstanceGroup
|
model = models.InstanceGroup
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class ApiVersionRootView(APIView):
|
|||||||
data['ping'] = reverse('api:api_v2_ping_view', request=request)
|
data['ping'] = reverse('api:api_v2_ping_view', request=request)
|
||||||
data['instances'] = reverse('api:instance_list', request=request)
|
data['instances'] = reverse('api:instance_list', request=request)
|
||||||
data['instance_groups'] = reverse('api:instance_group_list', request=request)
|
data['instance_groups'] = reverse('api:instance_group_list', request=request)
|
||||||
|
data['receptor_addresses'] = reverse('api:receptor_addresses_list', request=request)
|
||||||
data['config'] = reverse('api:api_v2_config_view', request=request)
|
data['config'] = reverse('api:api_v2_config_view', request=request)
|
||||||
data['settings'] = reverse('api:setting_category_list', request=request)
|
data['settings'] = reverse('api:setting_category_list', request=request)
|
||||||
data['me'] = reverse('api:user_me_list', request=request)
|
data['me'] = reverse('api:user_me_list', request=request)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ from awx.main.models import (
|
|||||||
Project,
|
Project,
|
||||||
ProjectUpdate,
|
ProjectUpdate,
|
||||||
ProjectUpdateEvent,
|
ProjectUpdateEvent,
|
||||||
|
ReceptorAddress,
|
||||||
Role,
|
Role,
|
||||||
Schedule,
|
Schedule,
|
||||||
SystemJob,
|
SystemJob,
|
||||||
@@ -2430,6 +2431,29 @@ class InventoryUpdateEventAccess(BaseAccess):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ReceptorAddressAccess(BaseAccess):
|
||||||
|
"""
|
||||||
|
I can see receptor address records whenever I can access the instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
model = ReceptorAddress
|
||||||
|
|
||||||
|
def filtered_queryset(self):
|
||||||
|
return self.model.objects.filter(Q(instance__in=Instance.accessible_pk_qs(self.user, 'read_role')))
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
|
def can_add(self, data):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
|
def can_change(self, obj, data):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
|
def can_delete(self, obj):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class SystemJobEventAccess(BaseAccess):
|
class SystemJobEventAccess(BaseAccess):
|
||||||
"""
|
"""
|
||||||
I can only see manage System Jobs events if I'm a super user
|
I can only see manage System Jobs events if I'm a super user
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved
|
# All Rights Reserved
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
@@ -28,11 +26,20 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _add_address(self, **kwargs):
|
def _add_address(self, **kwargs):
|
||||||
i = Instance.objects.get(hostname=kwargs.pop('hostname'))
|
try:
|
||||||
kwargs['instance'] = i
|
instance = Instance.objects.get(hostname=kwargs.pop('hostname'))
|
||||||
ReceptorAddress.objects.create(**kwargs)
|
kwargs['instance'] = instance
|
||||||
|
addr = ReceptorAddress.objects.create(**kwargs)
|
||||||
|
print(f"Successfully added receptor address {addr.get_full_address()}")
|
||||||
|
self.changed = True
|
||||||
|
except Exception as e:
|
||||||
|
self.changed = False
|
||||||
|
print(f"Error adding receptor address: {e}")
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, **options):
|
def handle(self, **options):
|
||||||
|
self.changed = False
|
||||||
address_options = {k: options[k] for k in ('hostname', 'address', 'port', 'protocol', 'websocket_path', 'is_internal')}
|
address_options = {k: options[k] for k in ('hostname', 'address', 'port', 'protocol', 'websocket_path', 'is_internal')}
|
||||||
self._add_address(**address_options)
|
self._add_address(**address_options)
|
||||||
|
if self.changed:
|
||||||
|
print("(changed: True)")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 4.2.5 on 2023-10-03 18:31
|
# Generated by Django 4.2.5 on 2023-10-04 06:51
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
@@ -22,4 +22,22 @@ class Migration(migrations.Migration):
|
|||||||
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='receptor_addresses', to='main.instance')),
|
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='receptor_addresses', to='main.instance')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='receptoraddress',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
condition=models.Q(('port', None)),
|
||||||
|
fields=('address', 'protocol', 'websocket_path'),
|
||||||
|
name='unique_receptor_address_no_port',
|
||||||
|
violation_error_message='Receptor address must be unique.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='receptoraddress',
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
condition=models.Q(('port', None), _negated=True),
|
||||||
|
fields=('address', 'port', 'protocol', 'websocket_path'),
|
||||||
|
name='unique_receptor_address_with_port',
|
||||||
|
violation_error_message='Receptor address must be unique.',
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,27 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from awx.api.versioning import reverse
|
||||||
|
from django.db.models import Sum, Q
|
||||||
|
|
||||||
|
|
||||||
class ReceptorAddress(models.Model):
|
class ReceptorAddress(models.Model):
|
||||||
|
class Meta:
|
||||||
|
app_label = 'main'
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["address", "protocol", "websocket_path"],
|
||||||
|
condition=Q(port=None),
|
||||||
|
name="unique_receptor_address_no_port",
|
||||||
|
violation_error_message=_("Receptor address must be unique."),
|
||||||
|
),
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["address", "port", "protocol", "websocket_path"],
|
||||||
|
condition=~Q(port=None),
|
||||||
|
name="unique_receptor_address_with_port",
|
||||||
|
violation_error_message=_("Receptor address must be unique."),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
address = models.CharField(max_length=255)
|
address = models.CharField(max_length=255)
|
||||||
port = models.IntegerField(null=True)
|
port = models.IntegerField(null=True)
|
||||||
protocol = models.CharField(max_length=10)
|
protocol = models.CharField(max_length=10)
|
||||||
@@ -35,3 +55,6 @@ class ReceptorAddress(models.Model):
|
|||||||
return 'ws-peer'
|
return 'ws-peer'
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_absolute_url(self, request=None):
|
||||||
|
return reverse('api:receptor_address_detail', kwargs={'pk': self.pk}, request=request)
|
||||||
|
|||||||
Reference in New Issue
Block a user