Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 122 additions & 3 deletions awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
CredentialInputSource,
CredentialType,
ExecutionEnvironment,
ExecutionEnvironmentBuilder,
ExecutionEnvironmentBuilderBuild,
ExecutionEnvironmentBuilderBuildEvent,
Group,
Host,
HostMetric,
Expand Down Expand Up @@ -373,6 +376,7 @@ def get_type_choices(self):
'workflow_job': _('Workflow Job'),
'workflow_job_template': _('Workflow Template'),
'job_template': _('Job Template'),
'execution_environment_builder_build': _('EE Build'),
}
choices = []
for t in self.get_types():
Expand Down Expand Up @@ -798,7 +802,7 @@ class Meta:

def get_types(self):
if type(self) is UnifiedJobSerializer:
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job', 'workflow_job']
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job', 'workflow_job', 'execution_environment_builder_build']
else:
return super(UnifiedJobSerializer, self).get_types()

Expand Down Expand Up @@ -907,7 +911,7 @@ def get_field_names(self, declared_fields, info):

def get_types(self):
if type(self) is UnifiedJobListSerializer:
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job', 'workflow_job']
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job', 'workflow_job', 'execution_environment_builder_build']
else:
return super(UnifiedJobListSerializer, self).get_types()

Expand All @@ -928,6 +932,8 @@ def get_sub_serializer(self, obj):
serializer_class = WorkflowJobListSerializer
elif isinstance(obj, WorkflowApproval):
serializer_class = WorkflowApprovalListSerializer
elif isinstance(obj, ExecutionEnvironmentBuilderBuild):
serializer_class = ExecutionEnvironmentBuilderBuildListSerializer
return serializer_class

def to_representation(self, obj):
Expand All @@ -950,7 +956,7 @@ class Meta:

def get_types(self):
if type(self) is UnifiedJobStdoutSerializer:
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job']
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job', 'execution_environment_builder_build']
else:
return super(UnifiedJobStdoutSerializer, self).get_types()

Expand Down Expand Up @@ -1652,6 +1658,103 @@ class Meta:
fields = ('can_cancel',)


class ExecutionEnvironmentBuilderSerializer(BaseSerializer):
show_capabilities = ['start', 'edit', 'delete', 'copy']
capabilities_prefetch = ['admin']

class Meta:
model = ExecutionEnvironmentBuilder
fields = (
'*',
'organization',
'image',
'tag',
'credential',
'definition',
)

def get_related(self, obj):
res = super(ExecutionEnvironmentBuilderSerializer, self).get_related(obj)
res.update(
dict(
access_list=self.reverse('api:execution_environment_builder_access_list', kwargs={'pk': obj.pk}),
object_roles=self.reverse('api:execution_environment_builder_object_roles_list', kwargs={'pk': obj.pk}),
)
)
if obj.organization:
res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
if obj.credential:
res['credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.credential.pk})
return res


class ExecutionEnvironmentBuilderBuildSerializer(UnifiedJobSerializer):
type = serializers.SerializerMethodField()
execution_environment_builder = serializers.PrimaryKeyRelatedField(
queryset=ExecutionEnvironmentBuilder.objects.all(),
required=True,
)

class Meta:
model = ExecutionEnvironmentBuilderBuild
fields = (
'*',
'execution_environment_builder',
'-unified_job_template',
)

def get_type(self, obj):
return 'execution_environment_builder_build'


Comment on lines +1708 to +1709
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExecutionEnvironmentBuilderBuild responses won't include a related.stdout URL (UnifiedJobSerializer only adds it for Job/ProjectUpdate/InventoryUpdate/AdHocCommand). The UI OutputToolbar only renders the “Download Output” button when job.related.stdout is present, so EE builder builds will lack the download action. Consider overriding get_related() in ExecutionEnvironmentBuilderBuildSerializer (or extending UnifiedJobSerializer.get_related) to add the execution_environment_builder_build_stdout link.

Suggested change
def get_related(self, obj):
res = super().get_related(obj)
res['stdout'] = self.reverse('api:execution_environment_builder_build_stdout', kwargs={'pk': obj.pk})
return res

Copilot uses AI. Check for mistakes.
class ExecutionEnvironmentBuilderBuildDetailSerializer(ExecutionEnvironmentBuilderBuildSerializer):
class Meta:
model = ExecutionEnvironmentBuilderBuild
fields = (
'*',
'execution_environment_builder',
'-unified_job_template',
)

def get_summary_fields(self, obj):
data = super().get_summary_fields(obj)
if obj.execution_environment_builder:
builder_summary = {
'id': obj.execution_environment_builder.id,
'name': obj.execution_environment_builder.name,
'image': obj.execution_environment_builder.image,
'tag': obj.execution_environment_builder.tag,
}
if obj.execution_environment_builder.credential:
builder_summary['summary_fields'] = {
'credential': {
'id': obj.execution_environment_builder.credential.id,
'name': obj.execution_environment_builder.credential.name,
'kind': obj.execution_environment_builder.credential.kind,
}
}
data['execution_environment_builder'] = builder_summary
return data


class ExecutionEnvironmentBuilderBuildListSerializer(ExecutionEnvironmentBuilderBuildSerializer, UnifiedJobListSerializer):
type = serializers.SerializerMethodField()

class Meta:
model = ExecutionEnvironmentBuilderBuild
fields = ('*', '-controller_node', '-unified_job_template')


class ExecutionEnvironmentBuilderBuildCancelSerializer(serializers.Serializer):
can_cancel = serializers.BooleanField(read_only=True)


class ExecutionEnvironmentBuilderBuildRelaunchSerializer(BaseSerializer):
class Meta:
model = ExecutionEnvironmentBuilderBuild
fields = ()


class BaseSerializerWithVariables(BaseSerializer):
def validate_variables(self, value):
return vars_validate_or_raise(value)
Expand Down Expand Up @@ -4392,6 +4495,22 @@ def get_event_data(self, obj):
return obj.event_data


class ExecutionEnvironmentBuilderBuildEventSerializer(JobEventSerializer):
stdout = serializers.SerializerMethodField()

class Meta:
model = ExecutionEnvironmentBuilderBuildEvent
fields = ('*', '-name', '-description', '-job', '-job_id', '-parent_uuid', '-parent', '-host', 'execution_environment_builder_build')

def get_related(self, obj):
res = super(JobEventSerializer, self).get_related(obj)
res['execution_environment_builder_build'] = self.reverse('api:execution_environment_builder_build_detail', kwargs={'pk': obj.execution_environment_builder_build_id})
return res

def get_stdout(self, obj):
return UriCleaner.remove_sensitive(obj.stdout)


class AdHocCommandEventSerializer(BaseSerializer):
event_display = serializers.CharField(source='get_event_display', read_only=True)

Expand Down
25 changes: 25 additions & 0 deletions awx/api/urls/execution_environment_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (c) 2023 Ctrl IQ, Inc.
# All Rights Reserved.

from django.urls import path

from awx.api.views import (
ExecutionEnvironmentBuilderList,
ExecutionEnvironmentBuilderDetail,
ExecutionEnvironmentBuilderAccessList,
ExecutionEnvironmentBuilderObjectRolesList,
ExecutionEnvironmentBuilderCopy,
ExecutionEnvironmentBuilderLaunch,
)


urls = [
path('', ExecutionEnvironmentBuilderList.as_view(), name='execution_environment_builder_list'),
path('<int:pk>/', ExecutionEnvironmentBuilderDetail.as_view(), name='execution_environment_builder_detail'),
path('<int:pk>/copy/', ExecutionEnvironmentBuilderCopy.as_view(), name='execution_environment_builder_copy'),
path('<int:pk>/launch/', ExecutionEnvironmentBuilderLaunch.as_view(), name='execution_environment_builder_launch'),
path('<int:pk>/access_list/', ExecutionEnvironmentBuilderAccessList.as_view(), name='execution_environment_builder_access_list'),
path('<int:pk>/object_roles/', ExecutionEnvironmentBuilderObjectRolesList.as_view(), name='execution_environment_builder_object_roles_list'),
]

__all__ = ['urls']
25 changes: 25 additions & 0 deletions awx/api/urls/execution_environment_builder_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (c) 2024 Ansible, Inc.
# All Rights Reserved.

from django.urls import path

from awx.api.views import (
ExecutionEnvironmentBuilderBuildList,
ExecutionEnvironmentBuilderBuildDetail,
ExecutionEnvironmentBuilderBuildCancel,
ExecutionEnvironmentBuilderBuildRelaunch,
ExecutionEnvironmentBuilderBuildStdout,
ExecutionEnvironmentBuilderBuildEventsList,
)


urls = [
path('', ExecutionEnvironmentBuilderBuildList.as_view(), name='execution_environment_builder_build_list'),
path('<int:pk>/', ExecutionEnvironmentBuilderBuildDetail.as_view(), name='execution_environment_builder_build_detail'),
path('<int:pk>/cancel/', ExecutionEnvironmentBuilderBuildCancel.as_view(), name='execution_environment_builder_build_cancel'),
path('<int:pk>/relaunch/', ExecutionEnvironmentBuilderBuildRelaunch.as_view(), name='execution_environment_builder_build_relaunch'),
path('<int:pk>/stdout/', ExecutionEnvironmentBuilderBuildStdout.as_view(), name='execution_environment_builder_build_stdout'),
path('<int:pk>/events/', ExecutionEnvironmentBuilderBuildEventsList.as_view(), name='execution_environment_builder_build_events_list'),
]

__all__ = ['urls']
4 changes: 4 additions & 0 deletions awx/api/urls/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
from .project_update import urls as project_update_urls
from .inventory import urls as inventory_urls, constructed_inventory_urls
from .execution_environments import urls as execution_environment_urls
from .execution_environment_builder import urls as execution_environment_builder_urls
from .execution_environment_builder_build import urls as execution_environment_builder_build_urls
from .team import urls as team_urls
from .host import urls as host_urls
from .host_metric import urls as host_metric_urls
Expand Down Expand Up @@ -118,6 +120,8 @@
path('organizations/', include(organization_urls)),
path('users/', include(user_urls)),
path('execution_environments/', include(execution_environment_urls)),
path('execution_environment_builders/', include(execution_environment_builder_urls)),
path('builds/', include(execution_environment_builder_build_urls)),
path('projects/', include(project_urls)),
path('project_updates/', include(project_update_urls)),
path('teams/', include(team_urls)),
Expand Down
119 changes: 119 additions & 0 deletions awx/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,121 @@ class ExecutionEnvironmentActivityStreamList(SubListAPIView):
filter_read_permission = False


class ExecutionEnvironmentBuilderList(ListCreateAPIView):
model = models.ExecutionEnvironmentBuilder
serializer_class = serializers.ExecutionEnvironmentBuilderSerializer


class ExecutionEnvironmentBuilderDetail(RetrieveUpdateDestroyAPIView):
model = models.ExecutionEnvironmentBuilder
serializer_class = serializers.ExecutionEnvironmentBuilderSerializer


class ExecutionEnvironmentBuilderAccessList(ResourceAccessList):
model = models.User
parent_model = models.ExecutionEnvironmentBuilder


class ExecutionEnvironmentBuilderObjectRolesList(SubListAPIView):
model = models.Role
serializer_class = serializers.RoleSerializer
parent_model = models.ExecutionEnvironmentBuilder
search_fields = ('role_field', 'content_type__model')

def get_queryset(self):
parent = self.get_parent_object()
content_type = ContentType.objects.get_for_model(self.parent_model)
return models.Role.objects.filter(content_type=content_type, object_id=parent.pk)


class ExecutionEnvironmentBuilderCopy(CopyAPIView):
model = models.ExecutionEnvironmentBuilder
copy_return_serializer_class = serializers.ExecutionEnvironmentBuilderSerializer


class ExecutionEnvironmentBuilderLaunch(GenericAPIView):
model = models.ExecutionEnvironmentBuilder
serializer_class = serializers.EmptySerializer
obj_permission_type = 'start'

def get(self, request, *args, **kwargs):
return Response({})

def post(self, request, *args, **kwargs):
obj = self.get_object()
new_build = models.ExecutionEnvironmentBuilderBuild.objects.create(
execution_environment_builder=obj,
name=request.data.get('name', f'{obj.name} Build'),
created_by=request.user,
modified_by=request.user,
)
new_build.signal_start()
data = OrderedDict()
data['execution_environment_builder_build'] = new_build.id
return Response(data, status=status.HTTP_201_CREATED)


class ExecutionEnvironmentBuilderBuildList(ListCreateAPIView):
model = models.ExecutionEnvironmentBuilderBuild
serializer_class = serializers.ExecutionEnvironmentBuilderBuildListSerializer

def perform_create(self, serializer):
obj = serializer.save(created_by=self.request.user, modified_by=self.request.user)
obj.signal_start()

class ExecutionEnvironmentBuilderBuildDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
model = models.ExecutionEnvironmentBuilderBuild
serializer_class = serializers.ExecutionEnvironmentBuilderBuildDetailSerializer


class ExecutionEnvironmentBuilderBuildCancel(GenericCancelView):
model = models.ExecutionEnvironmentBuilderBuild
serializer_class = serializers.ExecutionEnvironmentBuilderBuildCancelSerializer


class ExecutionEnvironmentBuilderBuildRelaunch(GenericAPIView):
model = models.ExecutionEnvironmentBuilderBuild
serializer_class = serializers.ExecutionEnvironmentBuilderBuildRelaunchSerializer
obj_permission_type = 'start'

def get(self, request, *args, **kwargs):
obj = self.get_object()
return Response({})

def post(self, request, *args, **kwargs):
obj = self.get_object()
# Create a new build with the same configuration as the original
builder = obj.execution_environment_builder
new_build = models.ExecutionEnvironmentBuilderBuild.objects.create(
execution_environment_builder=builder,
name=f"{builder.name if builder else ''}",
launch_type='relaunch',
created_by=request.user,
modified_by=request.user,
)
new_build.signal_start()
return Response({'id': new_build.id})


class ExecutionEnvironmentBuilderBuildEventsList(SubListAPIView):
model = models.ExecutionEnvironmentBuilderBuildEvent
serializer_class = serializers.ExecutionEnvironmentBuilderBuildEventSerializer
parent_model = models.ExecutionEnvironmentBuilderBuild
relationship = 'execution_environment_builder_build_events'
name = _('Execution Environment Builder Build Events List')
search_fields = ('stdout',)
pagination_class = UnifiedJobEventPagination

def finalize_response(self, request, response, *args, **kwargs):
response['X-UI-Max-Events'] = settings.MAX_UI_JOB_EVENTS
return super(ExecutionEnvironmentBuilderBuildEventsList, self).finalize_response(request, response, *args, **kwargs)

def get_queryset(self):
build = self.get_parent_object()
self.check_parent_access(build)
return build.get_event_queryset()


class ProjectList(ListCreateAPIView):
model = models.Project
serializer_class = serializers.ProjectSerializer
Expand Down Expand Up @@ -4304,6 +4419,10 @@ class AdHocCommandStdout(UnifiedJobStdout):
model = models.AdHocCommand


class ExecutionEnvironmentBuilderBuildStdout(UnifiedJobStdout):
model = models.ExecutionEnvironmentBuilderBuild


class NotificationTemplateList(ListCreateAPIView):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
Expand Down
2 changes: 2 additions & 0 deletions awx/api/views/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def get(self, request, format=None):
data['organizations'] = reverse('api:organization_list', request=request)
data['users'] = reverse('api:user_list', request=request)
data['execution_environments'] = reverse('api:execution_environment_list', request=request)
data['execution_environment_builders'] = reverse('api:execution_environment_builder_list', request=request)
data['builds'] = reverse('api:execution_environment_builder_build_list', request=request)
data['projects'] = reverse('api:project_list', request=request)
data['project_updates'] = reverse('api:project_update_list', request=request)
data['teams'] = reverse('api:team_list', request=request)
Expand Down
Loading