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
183 changes: 180 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 @@ -1615,6 +1621,25 @@ def data(self):
return ReturnList(ret, serializer=self)


class ProjectExecutionEnvironmentFilesSerializer(ProjectSerializer):
execution_environment_files = serializers.SerializerMethodField(
help_text=_('Array of ansible-builder execution environment definition files available within this project.')
)

class Meta:
model = Project
fields = ('execution_environment_files',)

def get_execution_environment_files(self, obj):
return obj.execution_environment_files

@property
def data(self):
ret = super(ProjectExecutionEnvironmentFilesSerializer, self).data
ret = ret.get('execution_environment_files', [])
return ReturnList(ret, serializer=self)


class ProjectUpdateViewSerializer(ProjectSerializer):
can_update = serializers.BooleanField(read_only=True)

Expand Down Expand Up @@ -1673,6 +1698,140 @@ 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',
'project',
'execution_environment_file',
)

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})
if obj.project:
res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk})
return res

def validate(self, attrs):
attrs = super(ExecutionEnvironmentBuilderSerializer, self).validate(attrs)
project = attrs.get('project', getattr(self.instance, 'project', None))
ee_file = attrs.get('execution_environment_file', getattr(self.instance, 'execution_environment_file', None))
if ee_file:
if not project:
raise serializers.ValidationError({'execution_environment_file': _('A project is required when specifying an execution environment file.')})
self._validate_execution_environment_file(project, ee_file)
return attrs

def _validate_execution_environment_file(self, project, ee_file):
import os
import yaml

project_path = project.get_project_path()
if not project_path:
raise serializers.ValidationError(
{'execution_environment_file': _('The project has not been synced yet; unable to read the execution environment file.')}
)
base = os.path.abspath(project_path)
full_path = os.path.normpath(os.path.join(base, ee_file))
# Prevent path traversal outside of the project directory.
if full_path != base and not full_path.startswith(base + os.sep):
raise serializers.ValidationError({'execution_environment_file': _('Invalid execution environment file path.')})
if not os.path.isfile(full_path):
raise serializers.ValidationError({'execution_environment_file': _('Execution environment file not found in project.')})
try:
with open(full_path, 'r') as f:
data = yaml.safe_load(f.read())
except Exception:
raise serializers.ValidationError({'execution_environment_file': _('Unable to parse the execution environment file as YAML.')})
if not isinstance(data, dict) or data.get('version') != 3:
raise serializers.ValidationError({'execution_environment_file': _('The execution environment file must define "version: 3" at the top level.')})


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'


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 @@ -4441,6 +4600,24 @@ 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']
2 changes: 2 additions & 0 deletions awx/api/urls/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ProjectDetail,
ProjectPlaybooks,
ProjectInventories,
ProjectExecutionEnvironmentFiles,
ProjectScmInventorySources,
ProjectTeamsList,
ProjectUpdateView,
Expand All @@ -27,6 +28,7 @@
path('<int:pk>/', ProjectDetail.as_view(), name='project_detail'),
path('<int:pk>/playbooks/', ProjectPlaybooks.as_view(), name='project_playbooks'),
path('<int:pk>/inventories/', ProjectInventories.as_view(), name='project_inventories'),
path('<int:pk>/execution_environment_files/', ProjectExecutionEnvironmentFiles.as_view(), name='project_execution_environment_files'),
path('<int:pk>/scm_inventory_sources/', ProjectScmInventorySources.as_view(), name='project_scm_inventory_sources'),
path('<int:pk>/teams/', ProjectTeamsList.as_view(), name='project_teams_list'),
path('<int:pk>/update/', ProjectUpdateView.as_view(), name='project_update_view'),
Expand Down
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, federated_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
Loading