Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[configuration] delayed postprocs, extra volmounts #738

Merged
merged 1 commit into from
Dec 18, 2021
Merged
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
10 changes: 10 additions & 0 deletions src/ansible_navigator/configuration_subsystem/configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,17 @@ def _apply_cli_params(self) -> None:
self._messages.append(LogMessage(level=logging.INFO, message=message))

def _post_process(self) -> None:
delayed = []
relrod marked this conversation as resolved.
Show resolved Hide resolved
normal = []
relrod marked this conversation as resolved.
Show resolved Hide resolved

# Separate normal and delayed entries so they can be processed in that order.
for entry in self._config.entries:
if entry.delay_post_process:
delayed.append(entry)
else:
normal.append(entry)

for entry in normal + delayed:
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
if self._initial or entry.change_after_initial:
processor = getattr(self._config.post_processor, entry.name, None)
if callable(processor):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Entry(SimpleNamespace):
apply_to_subsequent_cli: Should this be applied to future CLIs parsed
choice: valid choices for this entry
cli_parameters: argparse specific params
delay_post_process: Post process in normal (alphabetical) order or wait until after first pass?
environment_variable_override: override the default environment variable
name: the reference name for the entry
settings_file_path_override: over the default settings file path
Expand All @@ -76,6 +77,7 @@ class Entry(SimpleNamespace):
change_after_initial: bool = True
choices: List = []
cli_parameters: Union[None, CliParameters] = None
delay_post_process: bool = False
environment_variable_override: Union[None, str] = None
settings_file_path_override: Union[None, str] = None
subcommands: Union[List[str], Constants] = Constants.ALL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ class Internals(SimpleNamespace):
Entry(
name="execution_environment_volume_mounts",
cli_parameters=CliParameters(action="append", nargs="+", short="--eev"),
delay_post_process=True,
settings_file_path_override="execution-environment.volume-mounts",
short_description=(
"Specify volume to be bind mounted within an execution environment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import shlex
import shutil

from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import List
from typing import Tuple
Expand Down Expand Up @@ -46,13 +48,64 @@ def wrapper(*args, **kwargs):
return wrapper


class VolumeMountOption(Enum):
"""Options that can be tagged on to the end of volume mounts.
Usually these are used for things like selinux relabeling, but there are
some other valid options as well, which can and should be added here as
needed. See ``man podman-run`` and ``man docker-run`` for valid choices and
keep in mind that we support both runtimes.
"""

# Relabel as private
Z = "Z"

# Relabel as shared.
z = "z" # pylint: disable=invalid-name


@dataclass
relrod marked this conversation as resolved.
Show resolved Hide resolved
class VolumeMount:
"""Describes EE volume mounts."""

#: The name of the config option requiring this volume mount.
calling_option: str

#: The source path of the volume mount.
src: str

#: The destination path in the container for the volume mount.
dest: str

#: Options for the bind mount.
options: List[VolumeMountOption] = field(default_factory=list)

def exists(self) -> bool:
"""Determine if the volume mount source exists."""
return Path(self.src).exists()

def to_string(self) -> str:
"""Render the volume mount in a way that (docker|podman) understands."""
out = f"{self.src}:{self.dest}"
if self.options:
joined_opts = ",".join(o.value for o in self.options) # pylint: disable=not-an-iterable
out += f":{joined_opts}"
return out
relrod marked this conversation as resolved.
Show resolved Hide resolved


PostProcessorReturn = Tuple[List[LogMessage], List[ExitMessage]]


class NavigatorPostProcessor:
# pylint:disable=too-many-public-methods
"""application post processor"""

def __init__(self):
# Volume mounts accumulated from post processing various config entries.
# These get processed towards the end, in the (delayed)
# execution_environment_volume_mounts() post-processor.
self.extra_volume_mounts: List[VolumeMount] = []

@staticmethod
def _true_or_false(entry: Entry, config: ApplicationConfiguration) -> PostProcessorReturn:
# pylint: disable=unused-argument
Expand Down Expand Up @@ -233,12 +286,12 @@ def execution_environment_image(
entry.value.current = f"{entry.value.current}:latest"
return messages, exit_messages

@staticmethod
@_post_processor
def execution_environment_volume_mounts(
entry: Entry, config: ApplicationConfiguration
self, entry: Entry, config: ApplicationConfiguration
) -> PostProcessorReturn:
# pylint: disable=unused-argument
# pylint: disable=too-many-branches
relrod marked this conversation as resolved.
Show resolved Hide resolved
"""Post process set_environment_variable"""
messages: List[LogMessage] = []
exit_messages: List[ExitMessage] = []
Expand Down Expand Up @@ -305,6 +358,18 @@ def execution_environment_volume_mounts(
return messages, exit_messages

entry.value.current = parsed_volume_mounts

if self.extra_volume_mounts and entry.value.current is C.NOT_SET:
entry.value.current = []

for mount in self.extra_volume_mounts:
if not mount.exists():
exit_msg = (
f"The volume mount source path '{mount.src}', needed by "
f"{mount.calling_option}, does not exist."
)
relrod marked this conversation as resolved.
Show resolved Hide resolved
exit_messages.append(ExitMessage(message=exit_msg))
entry.value.current.append(mount.to_string())
return messages, exit_messages

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Tests for the navigator config post-processor."""

import pytest

from ansible_navigator.configuration_subsystem.navigator_post_processor import (
VolumeMount,
VolumeMountOption,
)


@pytest.mark.parametrize(
("volmount", "expected"),
(
(VolumeMount("test_option", "/foo", "/bar"), "/foo:/bar"),
(VolumeMount("test_option", "/foo", "/bar", [VolumeMountOption.z]), "/foo:/bar:z"),
(
VolumeMount("test_option", "/foo", "/bar", [VolumeMountOption.z, VolumeMountOption.Z]),
"/foo:/bar:z,Z",
),
(VolumeMount("test_option", "/foo", "/bar", []), "/foo:/bar"),
),
ids=(
"normal mount",
"mount with relabel option",
"mount with a list of options",
"mount with empty list of options",
),
)
def test_navigator_volume_mount_to_string(volmount, expected):
"""Make sure volume mount ``to_string`` is sane."""
assert volmount.to_string() == expected