Skip to content

Commit

Permalink
[configuration] delayed postprocs, extra volmounts
Browse files Browse the repository at this point in the history
Change:

- Allow post-processors to be delayed until after the first
  (alphabetical) pass. In the delayed pass, delayed post-processors
  are again executed alphabetically.

- In the interest of keeping configuration simple, we don't allow for
  multiple "levels" of delay. A post-processor is either run during the
  first pass, or delayed until after the first/normal pass.

- This also adds an "extra volmounts" variable to the navigator
  post-processor class, which can be used by other post-processors to
  signify that they want a volume to be mounted. The volume-mount post
  processor is now delayed to accommodate.

Test Plan:

- Using this in lint/navigator work, no new tests yet because there is
  no other post processor currently which adds an extra volume mount.

Signed-off-by: Rick Elrod <rick@elrod.me>
  • Loading branch information
relrod committed Dec 18, 2021
1 parent ca2ed0c commit 939e4b3
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 2 deletions.
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 = []
normal = []

# 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:
if self._initial or entry.change_after_initial:
processor = getattr(self._config.post_processor, entry.name, None)
if callable(processor):
Expand Down
2 changes: 2 additions & 0 deletions src/ansible_navigator/configuration_subsystem/definitions.py
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
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


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
"""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."
)
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

0 comments on commit 939e4b3

Please sign in to comment.