diff --git a/docs/collection_metadata.rst b/docs/collection_metadata.rst index 37cb808f..c98ee29f 100644 --- a/docs/collection_metadata.rst +++ b/docs/collection_metadata.rst @@ -9,6 +9,7 @@ For Ansible Builder to find and install collection dependencies, those dependenc * The ``meta/execution-environment.yml`` file containing the Python and/or bindep requirements or referencing other files listing them. + The ``.yaml`` extension is also valid on this file. * The ``requirements.txt`` file in the root level of the collection. * The ``bindep.txt`` file in the root level of the collection. diff --git a/docs/definition.rst b/docs/definition.rst index bbb0593e..1e8e95c6 100644 --- a/docs/definition.rst +++ b/docs/definition.rst @@ -3,7 +3,9 @@ Execution environment definition ================================ -You define the content of your execution environment in a YAML file. By default, this file is called ``execution-environment.yml``. This file tells Ansible Builder how to create the build instruction file (``Containerfile`` for Podman, ``Dockerfile`` for Docker) and build context for your container image. +You define the content of your execution environment in a YAML file. By default, this file is called ``execution-environment.yml`` +or ``execution-environment.yaml``. This file tells Ansible Builder how to create the build instruction file +(``Containerfile`` for Podman, ``Dockerfile`` for Docker) and build context for your container image. .. note:: This page documents the definition schema for Ansible Builder 3.x. If you are running an older version of Ansible Builder, you need an older schema version. Please consult older versions of the docs for more information. We recommend using version 3, which offers substantially more configurability and functionality than previous versions. diff --git a/docs/index.rst b/docs/index.rst index f8ea657d..c31947be 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,10 +36,10 @@ Refer to the `Getting started with Execution Environments guide ` file. -By default, this file is called ``execution-environment.yml``. +By default, this file is called ``execution-environment.yml`` (the ``.yaml`` extension is also accepted). In the execution environment definition file, you can specify the exact content you want to include in your execution environment. You can specify these items: diff --git a/docs/usage.rst b/docs/usage.rst index 0bddd9ce..f4f9f3be 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,7 +18,7 @@ The ``ansible-builder build`` command: * creates a build context necessary for building an execution environment image, * builds the image. -By default, it looks for a file named ``execution-environment.yml`` in the current directory. +By default, it looks for a file named ``execution-environment.yml`` (or ``execution-environment.yaml``) in the current directory. To build an execution environment using the default definition file, run: @@ -52,7 +52,7 @@ More recent versions of ``ansible-builder`` support multiple tags: ``--file`` ********** -Specifies the execution environment file. To use a file named something other than ``execution-environment.yml``: +Specifies the execution environment file. To use a file other than the default: .. code:: diff --git a/src/ansible_builder/_target_scripts/introspect.py b/src/ansible_builder/_target_scripts/introspect.py index e41a6819..bbd9c95e 100644 --- a/src/ansible_builder/_target_scripts/introspect.py +++ b/src/ansible_builder/_target_scripts/introspect.py @@ -7,8 +7,8 @@ import requirements + base_collections_path = '/usr/share/ansible/collections' -default_file = 'execution-environment.yml' logger = logging.getLogger(__name__) @@ -142,11 +142,19 @@ class CollectionDefinition: def __init__(self, collection_path): self.reference_path = collection_path - meta_file = os.path.join(collection_path, 'meta', default_file) - if os.path.exists(meta_file): - with open(meta_file, 'r') as f: - self.raw = yaml.safe_load(f) - else: + + # NOTE: Filenames should match constants.DEAFULT_EE_BASENAME and constants.YAML_FILENAME_EXTENSIONS. + meta_file_base = os.path.join(collection_path, 'meta', 'execution-environment') + ee_exists = False + for ext in ('yml', 'yaml'): + meta_file = f"{meta_file_base}.{ext}" + if os.path.exists(meta_file): + with open(meta_file, 'r') as f: + self.raw = yaml.safe_load(f) + ee_exists = True + break + + if not ee_exists: self.raw = {'version': 1, 'dependencies': {}} # Automatically infer requirements for collection for entry, filename in [('python', 'requirements.txt'), ('system', 'bindep.txt')]: diff --git a/src/ansible_builder/cli.py b/src/ansible_builder/cli.py index 97726ba7..f4562e06 100644 --- a/src/ansible_builder/cli.py +++ b/src/ansible_builder/cli.py @@ -153,9 +153,8 @@ def add_container_options(parser): for p in [create_command_parser, build_command_parser]: p.add_argument('-f', '--file', - default=constants.default_file, dest='filename', - help='The definition of the execution environment (default: %(default)s)') + help='The definition of the execution environment (default: execution-environment.(yml|yaml))') p.add_argument('-c', '--context', default=constants.default_build_context, diff --git a/src/ansible_builder/constants.py b/src/ansible_builder/constants.py index b499aabd..da37ab3c 100644 --- a/src/ansible_builder/constants.py +++ b/src/ansible_builder/constants.py @@ -1,6 +1,5 @@ import shutil -default_file = 'execution-environment.yml' default_tag = 'ansible-execution-env:latest' default_build_context = 'context' default_verbosity = 2 @@ -46,3 +45,6 @@ } FINAL_IMAGE_BIN_PATH = "/opt/builder/bin" + +DEFAULT_EE_BASENAME = "execution-environment" +YAML_FILENAME_EXTENSIONS = ('yml', 'yaml') diff --git a/src/ansible_builder/main.py b/src/ansible_builder/main.py index 8a8e214f..465225fc 100644 --- a/src/ansible_builder/main.py +++ b/src/ansible_builder/main.py @@ -16,7 +16,7 @@ class AnsibleBuilder: def __init__(self, action: str, - filename: str = constants.default_file, + filename: str | None = None, build_args: dict[str, str] | None = None, build_context: str = constants.default_build_context, tag: list | None = None, diff --git a/src/ansible_builder/user_definition.py b/src/ansible_builder/user_definition.py index 30c08466..399283d7 100644 --- a/src/ansible_builder/user_definition.py +++ b/src/ansible_builder/user_definition.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os import textwrap @@ -63,27 +65,43 @@ class UserDefinition: Class representing the Execution Environment file. """ - def __init__(self, filename): + def __init__(self, filename=None): """ Initialize the UserDefinition object. - :param str filename: Path to the EE file. + :param str filename: Path to the EE file. If this is None, we will + look for the default file: execution-environment.(yml|yaml) """ - self.filename = filename # A dict that is the raw representation of the EE file. self.raw = {} + + if filename is None: + for ext in constants.YAML_FILENAME_EXTENSIONS: + ee_file = f'{constants.DEFAULT_EE_BASENAME}.{ext}' + if os.path.exists(ee_file): + filename = ee_file + break + if filename is None: + raise DefinitionError(textwrap.dedent( + """ + Default execution environment file not found in current directory. + Use -f to specify the correct file. + """)) + + self.filename = filename + # The folder which dependencies are specified relative to. - self.reference_path = os.path.dirname(filename) + self.reference_path = os.path.dirname(self.filename) try: - with open(filename, 'r') as ee_file: + with open(self.filename, 'r') as ee_file: data = yaml.safe_load(ee_file) self.raw = data if data else {} except FileNotFoundError as exc: raise DefinitionError(textwrap.dedent( f""" - Could not detect '{filename}' file in this directory. + Could not detect '{self.filename}' file in this directory. Use -f to specify a different location. """)) from exc except (yaml.parser.ParserError, yaml.scanner.ScannerError) as exc: diff --git a/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/MANIFEST.json b/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/MANIFEST.json new file mode 100644 index 00000000..e69de29b diff --git a/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/meta/execution-environment.yaml b/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/meta/execution-environment.yaml new file mode 100644 index 00000000..a9b41f4a --- /dev/null +++ b/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/meta/execution-environment.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + python: my-requirements.txt diff --git a/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/my-requirements.txt b/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/my-requirements.txt new file mode 100644 index 00000000..787c5308 --- /dev/null +++ b/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/my-requirements.txt @@ -0,0 +1 @@ +python-six diff --git a/test/unit/test_introspect.py b/test/unit/test_introspect.py index 62dcc494..36ea823e 100644 --- a/test/unit/test_introspect.py +++ b/test/unit/test_introspect.py @@ -67,3 +67,19 @@ def test_parse_args_default_action(): assert parser.user_bindep == user_bindep assert parser.write_pip == write_pip assert parser.write_bindep == write_bindep + + +def test_yaml_extension(data_dir): + """ + Test that introspection recognizes a collection meta directory EE with a .yaml file extension. + + NOTE: This test depends on the meta EE in the collection to reference a file other than "requirements.txt" + because of the way CollectionDefinition.__init__() will fall through to a default if the meta EE is not + found. + """ + col_path = os.path.join(data_dir, 'alternate_collections') + files = process(col_path) + assert files == { + 'python': {'test_collection.test_yaml_extension': ['python-six']}, + 'system': {}, + } diff --git a/test/unit/test_user_definition.py b/test/unit/test_user_definition.py index 1937e2ac..db21b9bd 100644 --- a/test/unit/test_user_definition.py +++ b/test/unit/test_user_definition.py @@ -1,6 +1,7 @@ import os import pytest +from ansible_builder import constants from ansible_builder.exceptions import DefinitionError from ansible_builder.main import AnsibleBuilder from ansible_builder.user_definition import UserDefinition, ImageDescription @@ -306,3 +307,35 @@ def test_missing_orig_name_tag(self, key, image): with pytest.raises(DefinitionError) as error: ImageDescription(ee_section, key) assert f"Container image requires a tag: {image}" in str(error.value.args[0]) + + +class TestDefaultEEFilename: + """ + Class to handle test setup/teardown of tests that need to change working + directory to test the default EE filename in the current directory. + """ + + def setup_method(self): + self._old_workdir = os.getcwd() # pylint: disable=W0201 + + def teardown_method(self): + os.chdir(self._old_workdir) + + def test_all_default_filenames(self, tmp_path): + """ + Test finding all variations of the default execution environment filename. + """ + os.chdir(str(tmp_path)) + for ext in constants.YAML_FILENAME_EXTENSIONS: + ee = tmp_path / f"{constants.DEFAULT_EE_BASENAME}.{ext}" + ee.write_text("version: 3") + UserDefinition() # This would fail if the file is not found + ee.unlink() + + def test_missing_default_file(self, tmp_path): + """ + Test that we fail if the default file is not found. + """ + os.chdir(str(tmp_path)) + with pytest.raises(DefinitionError, match="Default execution environment file not found in current directory."): + UserDefinition()