mirror of
https://github.com/ansible/awx.git
synced 2026-04-14 06:29:25 -02:30
Merge pull request #11659 from ansible/expose_isolate_path_k8s
Allow isolated paths as hostPath volume @ k8s/ocp/container groups
This commit is contained in:
@@ -334,6 +334,19 @@ register(
|
|||||||
category_slug='jobs',
|
category_slug='jobs',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
register(
|
||||||
|
'AWX_MOUNT_ISOLATED_PATHS_ON_K8S',
|
||||||
|
field_class=fields.BooleanField,
|
||||||
|
default=False,
|
||||||
|
label=_('Expose host paths for Container Groups'),
|
||||||
|
help_text=_(
|
||||||
|
'Expose paths via hostPath for the Pods created by a Container Group. '
|
||||||
|
'HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. '
|
||||||
|
),
|
||||||
|
category=_('Jobs'),
|
||||||
|
category_slug='jobs',
|
||||||
|
)
|
||||||
|
|
||||||
register(
|
register(
|
||||||
'GALAXY_IGNORE_CERTS',
|
'GALAXY_IGNORE_CERTS',
|
||||||
field_class=fields.BooleanField,
|
field_class=fields.BooleanField,
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ JOB_FOLDER_PREFIX = 'awx_%s_'
|
|||||||
|
|
||||||
# :z option tells Podman that two containers share the volume content with r/w
|
# :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.
|
# :O option tells Podman to mount the directory from the host as a temporary storage using the overlay file system.
|
||||||
|
# :ro or :rw option to mount a volume in read-only or read-write mode, respectively. By default, the volumes are mounted read-write.
|
||||||
# see podman-run manpage for further details
|
# see podman-run manpage for further details
|
||||||
# /HOST-DIR:/CONTAINER-DIR:OPTIONS
|
# /HOST-DIR:/CONTAINER-DIR:OPTIONS
|
||||||
CONTAINER_VOLUMES_MOUNT_TYPES = ['z', 'O']
|
CONTAINER_VOLUMES_MOUNT_TYPES = ['z', 'O', 'ro', 'rw']
|
||||||
MAX_ISOLATED_PATH_COLON_DELIMITER = 2
|
MAX_ISOLATED_PATH_COLON_DELIMITER = 2
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from awx.main.constants import (
|
|||||||
STANDARD_INVENTORY_UPDATE_ENV,
|
STANDARD_INVENTORY_UPDATE_ENV,
|
||||||
JOB_FOLDER_PREFIX,
|
JOB_FOLDER_PREFIX,
|
||||||
MAX_ISOLATED_PATH_COLON_DELIMITER,
|
MAX_ISOLATED_PATH_COLON_DELIMITER,
|
||||||
|
CONTAINER_VOLUMES_MOUNT_TYPES,
|
||||||
)
|
)
|
||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
Instance,
|
Instance,
|
||||||
@@ -163,8 +164,14 @@ class BaseTask(object):
|
|||||||
# Using z allows the dir to be mounted by multiple containers
|
# 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
|
||||||
if this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER:
|
if this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER:
|
||||||
src, dest, scontext = this_path.split(':')
|
src, dest, mount_option = this_path.split(':')
|
||||||
params['container_volume_mounts'].append(f'{src}:{dest}:{scontext}')
|
|
||||||
|
# mount_option validation via performed via API, but since this can be overriden via settings.py
|
||||||
|
if mount_option not in CONTAINER_VOLUMES_MOUNT_TYPES:
|
||||||
|
mount_option = 'z'
|
||||||
|
logger.warn(f'The path {this_path} has volume mount type {mount_option} which is not supported. Using "z" instead.')
|
||||||
|
|
||||||
|
params['container_volume_mounts'].append(f'{src}:{dest}:{mount_option}')
|
||||||
elif this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER - 1:
|
elif this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER - 1:
|
||||||
src, dest = this_path.split(':')
|
src, dest = this_path.split(':')
|
||||||
params['container_volume_mounts'].append(f'{src}:{dest}:z')
|
params['container_volume_mounts'].append(f'{src}:{dest}:z')
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ from awx.main.utils.common import (
|
|||||||
parse_yaml_or_json,
|
parse_yaml_or_json,
|
||||||
cleanup_new_process,
|
cleanup_new_process,
|
||||||
)
|
)
|
||||||
|
from awx.main.constants import MAX_ISOLATED_PATH_COLON_DELIMITER
|
||||||
|
|
||||||
|
|
||||||
# Receptorctl
|
# Receptorctl
|
||||||
from receptorctl.socket_interface import ReceptorControl
|
from receptorctl.socket_interface import ReceptorControl
|
||||||
@@ -478,6 +480,48 @@ class AWXReceptorJob:
|
|||||||
if self.task.instance.execution_environment.pull:
|
if self.task.instance.execution_environment.pull:
|
||||||
pod_spec['spec']['containers'][0]['imagePullPolicy'] = pull_options[self.task.instance.execution_environment.pull]
|
pod_spec['spec']['containers'][0]['imagePullPolicy'] = pull_options[self.task.instance.execution_environment.pull]
|
||||||
|
|
||||||
|
# This allows the user to also expose the isolated path list
|
||||||
|
# to EEs running in k8s/ocp environments, i.e. container groups.
|
||||||
|
# This assumes the node and SA supports hostPath volumes
|
||||||
|
# type is not passed due to backward compatibility,
|
||||||
|
# which means that no checks will be performed before mounting the hostPath volume.
|
||||||
|
if settings.AWX_MOUNT_ISOLATED_PATHS_ON_K8S and settings.AWX_ISOLATION_SHOW_PATHS:
|
||||||
|
spec_volume_mounts = []
|
||||||
|
spec_volumes = []
|
||||||
|
|
||||||
|
for idx, this_path in enumerate(settings.AWX_ISOLATION_SHOW_PATHS):
|
||||||
|
mount_option = None
|
||||||
|
if this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER:
|
||||||
|
src, dest, mount_option = this_path.split(':')
|
||||||
|
elif this_path.count(':') == MAX_ISOLATED_PATH_COLON_DELIMITER - 1:
|
||||||
|
src, dest = this_path.split(':')
|
||||||
|
else:
|
||||||
|
src = dest = this_path
|
||||||
|
|
||||||
|
# Enforce read-only volume if 'ro' has been explicitly passed
|
||||||
|
# We do this so we can use the same configuration for regular scenarios and k8s
|
||||||
|
# Since flags like ':O', ':z' or ':Z' are not valid in the k8s realm
|
||||||
|
# Example: /data:/data:ro
|
||||||
|
read_only = bool('ro' == mount_option)
|
||||||
|
|
||||||
|
# Since type is not being passed, k8s by default will not perform any checks if the
|
||||||
|
# hostPath volume exists on the k8s node itself.
|
||||||
|
spec_volumes.append({'name': f'volume-{idx}', 'hostPath': {'path': src}})
|
||||||
|
|
||||||
|
spec_volume_mounts.append({'name': f'volume-{idx}', 'mountPath': f'{dest}', 'readOnly': read_only})
|
||||||
|
|
||||||
|
# merge any volumes definition already present in the pod_spec
|
||||||
|
if 'volumes' in pod_spec['spec']:
|
||||||
|
pod_spec['spec']['volumes'] += spec_volumes
|
||||||
|
else:
|
||||||
|
pod_spec['spec']['volumes'] = spec_volumes
|
||||||
|
|
||||||
|
# merge any volumesMounts definition already present in the pod_spec
|
||||||
|
if 'volumeMounts' in pod_spec['spec']['containers'][0]:
|
||||||
|
pod_spec['spec']['containers'][0]['volumeMounts'] += spec_volume_mounts
|
||||||
|
else:
|
||||||
|
pod_spec['spec']['containers'][0]['volumeMounts'] = spec_volume_mounts
|
||||||
|
|
||||||
if self.task and self.task.instance.is_container_group_task:
|
if self.task and self.task.instance.is_container_group_task:
|
||||||
# If EE credential is passed, create an imagePullSecret
|
# If EE credential is passed, create an imagePullSecret
|
||||||
if self.task.instance.execution_environment and self.task.instance.execution_environment.credential:
|
if self.task.instance.execution_environment and self.task.instance.execution_environment.credential:
|
||||||
|
|||||||
@@ -1001,3 +1001,6 @@ DEFAULT_CONTROL_PLANE_QUEUE_NAME = 'controlplane'
|
|||||||
# For example, to disable SELinux in containers for podman
|
# For example, to disable SELinux in containers for podman
|
||||||
# DEFAULT_CONTAINER_RUN_OPTIONS = ['--security-opt', 'label=disable']
|
# DEFAULT_CONTAINER_RUN_OPTIONS = ['--security-opt', 'label=disable']
|
||||||
DEFAULT_CONTAINER_RUN_OPTIONS = ['--network', 'slirp4netns:enable_ipv6=true']
|
DEFAULT_CONTAINER_RUN_OPTIONS = ['--network', 'slirp4netns:enable_ipv6=true']
|
||||||
|
|
||||||
|
# Mount exposed paths as hostPath resource in k8s/ocp
|
||||||
|
AWX_MOUNT_ISOLATED_PATHS_ON_K8S = False
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ describe('<JobsDetail />', () => {
|
|||||||
assertDetail(wrapper, 'Default Project Update Timeout', '0 seconds');
|
assertDetail(wrapper, 'Default Project Update Timeout', '0 seconds');
|
||||||
assertDetail(wrapper, 'Per-Host Ansible Fact Cache Timeout', '0 seconds');
|
assertDetail(wrapper, 'Per-Host Ansible Fact Cache Timeout', '0 seconds');
|
||||||
assertDetail(wrapper, 'Maximum number of forks per job', '200');
|
assertDetail(wrapper, 'Maximum number of forks per job', '200');
|
||||||
|
assertDetail(wrapper, 'Expose host paths for Container Groups', 'Off');
|
||||||
assertVariableDetail(
|
assertVariableDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'Ansible Modules Allowed for Ad Hoc Jobs',
|
'Ansible Modules Allowed for Ad Hoc Jobs',
|
||||||
|
|||||||
@@ -212,6 +212,10 @@ function JobsEdit() {
|
|||||||
name="AWX_ISOLATION_SHOW_PATHS"
|
name="AWX_ISOLATION_SHOW_PATHS"
|
||||||
config={jobs.AWX_ISOLATION_SHOW_PATHS}
|
config={jobs.AWX_ISOLATION_SHOW_PATHS}
|
||||||
/>
|
/>
|
||||||
|
<BooleanField
|
||||||
|
name="AWX_MOUNT_ISOLATED_PATHS_ON_K8S"
|
||||||
|
config={jobs.AWX_MOUNT_ISOLATED_PATHS_ON_K8S}
|
||||||
|
/>
|
||||||
<ObjectField name="AWX_TASK_ENV" config={jobs.AWX_TASK_ENV} />
|
<ObjectField name="AWX_TASK_ENV" config={jobs.AWX_TASK_ENV} />
|
||||||
{submitError && <FormSubmitError error={submitError} />}
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
{revertError && <FormSubmitError error={revertError} />}
|
{revertError && <FormSubmitError error={revertError} />}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"AWX_ISOLATION_SHOW_PATHS": [],
|
"AWX_ISOLATION_SHOW_PATHS": [],
|
||||||
"AWX_ROLES_ENABLED": true,
|
"AWX_ROLES_ENABLED": true,
|
||||||
"AWX_SHOW_PLAYBOOK_LINKS": false,
|
"AWX_SHOW_PLAYBOOK_LINKS": false,
|
||||||
|
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false,
|
||||||
"AWX_TASK_ENV": {},
|
"AWX_TASK_ENV": {},
|
||||||
"DEFAULT_INVENTORY_UPDATE_TIMEOUT": 0,
|
"DEFAULT_INVENTORY_UPDATE_TIMEOUT": 0,
|
||||||
"DEFAULT_JOB_TIMEOUT": 0,
|
"DEFAULT_JOB_TIMEOUT": 0,
|
||||||
|
|||||||
@@ -276,6 +276,15 @@
|
|||||||
"category_slug": "jobs",
|
"category_slug": "jobs",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": {
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"label": "Expose host paths for Container Groups",
|
||||||
|
"help_text": "Expose paths via hostPath for the Pods created by a Container Group. HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. ",
|
||||||
|
"category": "Jobs",
|
||||||
|
"category_slug": "jobs",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"GALAXY_IGNORE_CERTS": {
|
"GALAXY_IGNORE_CERTS": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -3973,6 +3982,14 @@
|
|||||||
"category_slug": "jobs",
|
"category_slug": "jobs",
|
||||||
"defined_in_file": false
|
"defined_in_file": false
|
||||||
},
|
},
|
||||||
|
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": {
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Expose host paths for Container Groups",
|
||||||
|
"help_text": "Expose paths via hostPath for the Pods created by a Container Group. HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. ",
|
||||||
|
"category": "Jobs",
|
||||||
|
"category_slug": "jobs",
|
||||||
|
"defined_in_file": false
|
||||||
|
},
|
||||||
"GALAXY_IGNORE_CERTS": {
|
"GALAXY_IGNORE_CERTS": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Ignore Ansible Galaxy SSL Certificate Verification",
|
"label": "Ignore Ansible Galaxy SSL Certificate Verification",
|
||||||
|
|||||||
@@ -297,5 +297,6 @@
|
|||||||
"users":{"fields":["username"],"adj_list":[]},
|
"users":{"fields":["username"],"adj_list":[]},
|
||||||
"instances":{"fields":["hostname"],"adj_list":[]}
|
"instances":{"fields":["hostname"],"adj_list":[]}
|
||||||
},
|
},
|
||||||
"DEFAULT_EXECUTION_ENVIRONMENT": 1
|
"DEFAULT_EXECUTION_ENVIRONMENT": 1,
|
||||||
|
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,6 @@
|
|||||||
"DEFAULT_INVENTORY_UPDATE_TIMEOUT": 0,
|
"DEFAULT_INVENTORY_UPDATE_TIMEOUT": 0,
|
||||||
"DEFAULT_PROJECT_UPDATE_TIMEOUT": 0,
|
"DEFAULT_PROJECT_UPDATE_TIMEOUT": 0,
|
||||||
"ANSIBLE_FACT_CACHE_TIMEOUT": 0,
|
"ANSIBLE_FACT_CACHE_TIMEOUT": 0,
|
||||||
"MAX_FORKS": 200
|
"MAX_FORKS": 200,
|
||||||
|
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user