mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 09:57:35 -02:30
Merge pull request #11566 from ansible/expose_isolate_path_podman_O
Support user customization of EE mount options and mount paths
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -207,7 +207,7 @@ jobs:
|
|||||||
ansible-galaxy collection install -r molecule/requirements.yml
|
ansible-galaxy collection install -r molecule/requirements.yml
|
||||||
sudo rm -f $(which kustomize)
|
sudo rm -f $(which kustomize)
|
||||||
make kustomize
|
make kustomize
|
||||||
KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule test -s kind
|
KUSTOMIZE_PATH=$(readlink -f bin/kustomize) molecule -v test -s kind
|
||||||
env:
|
env:
|
||||||
AWX_TEST_IMAGE: awx
|
AWX_TEST_IMAGE: awx
|
||||||
AWX_TEST_VERSION: ci
|
AWX_TEST_VERSION: ci
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField, NullBooleanField # noqa
|
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField, NullBooleanField # noqa
|
||||||
from rest_framework.serializers import PrimaryKeyRelatedField # noqa
|
from rest_framework.serializers import PrimaryKeyRelatedField # noqa
|
||||||
|
|
||||||
|
# AWX
|
||||||
|
from awx.main.constants import CONTAINER_VOLUMES_MOUNT_TYPES, MAX_ISOLATED_PATH_COLON_DELIMITER
|
||||||
|
|
||||||
logger = logging.getLogger('awx.conf.fields')
|
logger = logging.getLogger('awx.conf.fields')
|
||||||
|
|
||||||
# Use DRF fields to convert/validate settings:
|
# Use DRF fields to convert/validate settings:
|
||||||
@@ -109,6 +112,49 @@ class StringListPathField(StringListField):
|
|||||||
self.fail('type_error', input_type=type(paths))
|
self.fail('type_error', input_type=type(paths))
|
||||||
|
|
||||||
|
|
||||||
|
class StringListIsolatedPathField(StringListField):
|
||||||
|
# Valid formats
|
||||||
|
# '/etc/pki/ca-trust'
|
||||||
|
# '/etc/pki/ca-trust:/etc/pki/ca-trust'
|
||||||
|
# '/etc/pki/ca-trust:/etc/pki/ca-trust:O'
|
||||||
|
|
||||||
|
default_error_messages = {
|
||||||
|
'type_error': _('Expected list of strings but got {input_type} instead.'),
|
||||||
|
'path_error': _('{path} is not a valid path choice. You must provide an absolute path.'),
|
||||||
|
'mount_error': _('{scontext} is not a valid mount option. Allowed types are {mount_types}'),
|
||||||
|
'syntax_error': _('Invalid syntax. A string HOST-DIR[:CONTAINER-DIR[:OPTIONS]] is expected but got {path}.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_internal_value(self, paths):
|
||||||
|
|
||||||
|
if isinstance(paths, (list, tuple)):
|
||||||
|
for p in paths:
|
||||||
|
if not isinstance(p, str):
|
||||||
|
self.fail('type_error', input_type=type(p))
|
||||||
|
if not p.startswith('/'):
|
||||||
|
self.fail('path_error', path=p)
|
||||||
|
|
||||||
|
if p.count(':'):
|
||||||
|
if p.count(':') > MAX_ISOLATED_PATH_COLON_DELIMITER:
|
||||||
|
self.fail('syntax_error', path=p)
|
||||||
|
try:
|
||||||
|
src, dest, scontext = p.split(':')
|
||||||
|
except ValueError:
|
||||||
|
scontext = 'z'
|
||||||
|
src, dest = p.split(':')
|
||||||
|
finally:
|
||||||
|
for sp in [src, dest]:
|
||||||
|
if not len(sp):
|
||||||
|
self.fail('syntax_error', path=sp)
|
||||||
|
if not sp.startswith('/'):
|
||||||
|
self.fail('path_error', path=sp)
|
||||||
|
if scontext not in CONTAINER_VOLUMES_MOUNT_TYPES:
|
||||||
|
self.fail('mount_error', scontext=scontext, mount_types=CONTAINER_VOLUMES_MOUNT_TYPES)
|
||||||
|
return super(StringListIsolatedPathField, self).to_internal_value(sorted(paths))
|
||||||
|
else:
|
||||||
|
self.fail('type_error', input_type=type(paths))
|
||||||
|
|
||||||
|
|
||||||
class URLField(CharField):
|
class URLField(CharField):
|
||||||
# these lines set up a custom regex that allow numbers in the
|
# these lines set up a custom regex that allow numbers in the
|
||||||
# top-level domain
|
# top-level domain
|
||||||
|
|||||||
@@ -259,10 +259,14 @@ register(
|
|||||||
|
|
||||||
register(
|
register(
|
||||||
'AWX_ISOLATION_SHOW_PATHS',
|
'AWX_ISOLATION_SHOW_PATHS',
|
||||||
field_class=fields.StringListField,
|
field_class=fields.StringListIsolatedPathField,
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Paths to expose to isolated jobs'),
|
label=_('Paths to expose to isolated jobs'),
|
||||||
help_text=_('List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.'),
|
help_text=_(
|
||||||
|
'List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line. '
|
||||||
|
'Volumes will be mounted from the execution node to the container. '
|
||||||
|
'The supported format is HOST-DIR[:CONTAINER-DIR[:OPTIONS]]. '
|
||||||
|
),
|
||||||
category=_('Jobs'),
|
category=_('Jobs'),
|
||||||
category_slug='jobs',
|
category_slug='jobs',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -85,3 +85,10 @@ RECEPTOR_PENDING = 'ansible-runner-???'
|
|||||||
# Naming pattern for AWX jobs in /tmp folder, like /tmp/awx_42_xiwm
|
# Naming pattern for AWX jobs in /tmp folder, like /tmp/awx_42_xiwm
|
||||||
# also update awxkit.api.pages.unified_jobs if changed
|
# also update awxkit.api.pages.unified_jobs if changed
|
||||||
JOB_FOLDER_PREFIX = 'awx_%s_'
|
JOB_FOLDER_PREFIX = 'awx_%s_'
|
||||||
|
|
||||||
|
# :z option tells Podman that two containers share the volume content with r/w
|
||||||
|
# :O option tells Podman to mount the directory from the host as a temporary storage using the overlay file system.
|
||||||
|
# see podman-run manpage for further details
|
||||||
|
# /HOST-DIR:/CONTAINER-DIR:OPTIONS
|
||||||
|
CONTAINER_VOLUMES_MOUNT_TYPES = ['z', 'O']
|
||||||
|
MAX_ISOLATED_PATH_COLON_DELIMITER = 2
|
||||||
|
|||||||
@@ -37,7 +37,13 @@ from gitdb.exc import BadName as BadGitName
|
|||||||
from awx.main.constants import ACTIVE_STATES
|
from awx.main.constants import ACTIVE_STATES
|
||||||
from awx.main.dispatch.publish import task
|
from awx.main.dispatch.publish import task
|
||||||
from awx.main.dispatch import get_local_queuename
|
from awx.main.dispatch import get_local_queuename
|
||||||
from awx.main.constants import PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_UPDATE_ENV, MINIMAL_EVENTS, JOB_FOLDER_PREFIX
|
from awx.main.constants import (
|
||||||
|
PRIVILEGE_ESCALATION_METHODS,
|
||||||
|
STANDARD_INVENTORY_UPDATE_ENV,
|
||||||
|
MINIMAL_EVENTS,
|
||||||
|
JOB_FOLDER_PREFIX,
|
||||||
|
MAX_ISOLATED_PATH_COLON_DELIMITER,
|
||||||
|
)
|
||||||
from awx.main.redact import UriCleaner
|
from awx.main.redact import UriCleaner
|
||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
Instance,
|
Instance,
|
||||||
@@ -147,6 +153,9 @@ class BaseTask(object):
|
|||||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), *args))
|
return os.path.abspath(os.path.join(os.path.dirname(__file__), *args))
|
||||||
|
|
||||||
def build_execution_environment_params(self, instance, private_data_dir):
|
def build_execution_environment_params(self, instance, private_data_dir):
|
||||||
|
"""
|
||||||
|
Return params structure to be executed by the container runtime
|
||||||
|
"""
|
||||||
if settings.IS_K8S:
|
if settings.IS_K8S:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -158,6 +167,9 @@ class BaseTask(object):
|
|||||||
"container_options": ['--user=root'],
|
"container_options": ['--user=root'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.DEFAULT_CONTAINER_RUN_OPTIONS:
|
||||||
|
params['container_options'].extend(settings.DEFAULT_CONTAINER_RUN_OPTIONS)
|
||||||
|
|
||||||
if instance.execution_environment.credential:
|
if instance.execution_environment.credential:
|
||||||
cred = instance.execution_environment.credential
|
cred = instance.execution_environment.credential
|
||||||
if all([cred.has_input(field_name) for field_name in ('host', 'username', 'password')]):
|
if all([cred.has_input(field_name) for field_name in ('host', 'username', 'password')]):
|
||||||
@@ -176,9 +188,17 @@ class BaseTask(object):
|
|||||||
if settings.AWX_ISOLATION_SHOW_PATHS:
|
if settings.AWX_ISOLATION_SHOW_PATHS:
|
||||||
params['container_volume_mounts'] = []
|
params['container_volume_mounts'] = []
|
||||||
for this_path in settings.AWX_ISOLATION_SHOW_PATHS:
|
for this_path in settings.AWX_ISOLATION_SHOW_PATHS:
|
||||||
# Using z allows the dir to mounted by multiple containers
|
# Verify if a mount path and SELinux context has been passed
|
||||||
|
# Using z allows the dir to be mounted by multiple containers
|
||||||
# Uppercase Z restricts access (in weird ways) to 1 container at a time
|
# Uppercase Z restricts access (in weird ways) to 1 container at a time
|
||||||
params['container_volume_mounts'].append(f'{this_path}:{this_path}:z')
|
if this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER:
|
||||||
|
src, dest, scontext = this_path.split(':')
|
||||||
|
params['container_volume_mounts'].append(f'{src}:{dest}:{scontext}')
|
||||||
|
elif this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER - 1:
|
||||||
|
src, dest = this_path.split(':')
|
||||||
|
params['container_volume_mounts'].append(f'{src}:{dest}:z')
|
||||||
|
else:
|
||||||
|
params['container_volume_mounts'].append(f'{this_path}:{this_path}:z')
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def build_private_data(self, instance, private_data_dir):
|
def build_private_data(self, instance, private_data_dir):
|
||||||
|
|||||||
@@ -989,3 +989,8 @@ DEFAULT_EXECUTION_QUEUE_NAME = 'default'
|
|||||||
DEFAULT_EXECUTION_QUEUE_POD_SPEC_OVERRIDE = ''
|
DEFAULT_EXECUTION_QUEUE_POD_SPEC_OVERRIDE = ''
|
||||||
# Name of the default controlplane queue
|
# Name of the default controlplane queue
|
||||||
DEFAULT_CONTROL_PLANE_QUEUE_NAME = 'controlplane'
|
DEFAULT_CONTROL_PLANE_QUEUE_NAME = 'controlplane'
|
||||||
|
|
||||||
|
# Extend container runtime attributes.
|
||||||
|
# For example, to disable SELinux in containers for podman
|
||||||
|
# DEFAULT_CONTAINER_RUN_OPTIONS = ['--security-opt', 'label=disable']
|
||||||
|
DEFAULT_CONTAINER_RUN_OPTIONS = []
|
||||||
|
|||||||
Reference in New Issue
Block a user