Repository: ansible/ansible-builder

Issue:
[Feature request]: add `execution-environment.yaml` to file lookup during the build
According to [YAML recommendations](https://yaml.org/faq.html) it would be great to support `yaml` file extension during lookup mechanism, for example, load files in that sequence:
1) `execution-environment.yaml`
2) `execution-environment.yml`

Many modern tools respect this, including Ansible, but ansible-builder does not support this yet.

Discussion:

Patch:
diff --git a/docs/collection_metadata.rst b/docs/collection_metadata.rst
index 37cb808..c98ee29 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 bbb0593..1e8e95c 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 f8ea657..c31947b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -36,10 +36,10 @@ Refer to the `Getting started with Execution Environments guide <https://docs.an
 Quickstart for Ansible Builder
 ==============================
 
-To get started with Ansible Builder, you must install the ``ansible-builder`` utility and a containerization tool. 
+To get started with Ansible Builder, you must install the ``ansible-builder`` utility and a containerization tool.
 
 Once you have the tools you need, create an :ref:`execution environment definition <builder_ee_definition>` 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 0bddd9c..f4f9f3b 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 e41a681..bbd9c95 100644
--- a/src/ansible_builder/_target_scripts/introspect.py
+++ b/src/ansible_builder/_target_scripts/introspect.py
@@ -7,8 +7,8 @@ import yaml
 
 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 97726ba..f4562e0 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 b499aab..da37ab3 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 @@ CONTEXT_FILES = {
 }
 
 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 8a8e214..465225f 100644
--- a/src/ansible_builder/main.py
+++ b/src/ansible_builder/main.py
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
 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 881e143..399283d 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:
@@ -245,6 +263,15 @@ class UserDefinition:
                     self.builder_image = ImageDescription(images, 'builder_image')
                     self.build_arg_defaults['EE_BUILDER_IMAGE'] = self.builder_image.name
 
+            if (
+                self.version >= 3
+                and self.build_arg_defaults["EE_BASE_IMAGE"] == 'quay.io/ansible/ansible-runner:latest'
+            ):
+                logging.warning(
+                    "Using the outdated base image '%s' might "
+                    "result in the build failures.", self.build_arg_defaults['EE_BASE_IMAGE']
+                )
+
             self._validate_additional_build_files()
 
             if self.version >= 3:


Test patch:
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 0000000..e69de29
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 0000000..a9b41f4
--- /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 0000000..787c530
--- /dev/null
+++ b/test/data/alternate_collections/ansible_collections/test_collection/test_yaml_extension/my-requirements.txt
@@ -0,0 +1,1 @@
+python-six
diff --git a/test/data/v3/pre_and_post/ee.yml b/test/data/v3/pre_and_post/ee.yml
index e4cfe43..0aa4d26 100644
--- a/test/data/v3/pre_and_post/ee.yml
+++ b/test/data/v3/pre_and_post/ee.yml
@@ -1,6 +1,10 @@
 ---
 version: 3
 
+images:
+  base_image:
+    name: quay.io/ansible/awx-ee:latest
+
 dependencies:
   galaxy: requirements.yml
 
diff --git a/test/integration/test_create.py b/test/integration/test_create.py
index 6a92e5e..155ce97 100644
--- a/test/integration/test_create.py
+++ b/test/integration/test_create.py
@@ -64,6 +64,9 @@ def test_inline_str_galaxy_requirements(cli, build_dir_and_ee_yml):
     """
     ee_str = """
     version: 3
+    images:
+      base_image:
+        name: 'base_image:latest'
     dependencies:
       galaxy: |
         collections:  # a comment
@@ -90,6 +93,9 @@ def test_inline_mapping_galaxy_requirements(cli, build_dir_and_ee_yml):
     """
     ee_str = """
     version: 3
+    images:
+      base_image:
+        name: 'base_image:latest'
     dependencies:
       galaxy:
         collections:
@@ -277,7 +283,7 @@ def test_v2_default_builder_image(cli, build_dir_and_ee_yml):
 def test_v3_pre_post_commands(cli, data_dir, tmp_path):
     """Test that the pre/post commands are inserted"""
     ee_def = data_dir / 'v3' / 'pre_and_post' / 'ee.yml'
-    r = cli(f'ansible-builder create -c {str(tmp_path)} -f {ee_def}')
+    r = cli(f'ansible-builder create -c {str(tmp_path)} -f {ee_def} --output-filename Containerfile')
     assert r.rc == 0
 
     containerfile = tmp_path / "Containerfile"
@@ -299,7 +305,7 @@ def test_v3_pre_post_commands(cli, data_dir, tmp_path):
 def test_v3_complete(cli, data_dir, tmp_path):
     """For testing various elements in a complete v2 EE file"""
     ee_def = data_dir / 'v3' / 'complete' / 'ee.yml'
-    r = cli(f'ansible-builder create -c {str(tmp_path)} -f {ee_def}')
+    r = cli(f'ansible-builder create -c {str(tmp_path)} -f {ee_def} --output-filename Containerfile')
     assert r.rc == 0
 
     containerfile = tmp_path / "Containerfile"
@@ -359,6 +365,9 @@ def test_v3_skip_ansible_check(cli, build_dir_and_ee_yml):
     """
     ee = [
         'version: 3',
+        'images:',
+        '  base_image:',
+        '    name: quay.io/ansible/awx-ee:latest',
         'options:',
         '  skip_ansible_check: True',
     ]
@@ -377,6 +386,9 @@ def test_v3_skip_container_init(cli, build_dir_and_ee_yml):
     tmpdir, eeyml = build_dir_and_ee_yml(
         """
         version: 3
+        images:
+          base_image:
+            name: quay.io/ansible/awx-ee:latest
         options:
           container_init: {}
         """
@@ -396,6 +408,9 @@ def test_v3_custom_container_init(cli, build_dir_and_ee_yml):
     tmpdir, eeyml = build_dir_and_ee_yml(
         """
         version: 3
+        images:
+          base_image:
+            name: quay.io/ansible/awx-ee:latest
         options:
           container_init:
             package_pip: custominit==1.2.3
@@ -422,6 +437,9 @@ def test_v3_no_relax_passwd_perms(cli, build_dir_and_ee_yml):
     """
     ee = """
     version: 3
+    images:
+      base_image:
+        name: quay.io/ansible/awx-ee:latest
     options:
         relax_passwd_permissions: false
     """
@@ -442,6 +460,9 @@ def test_v3_custom_workdir(cli, build_dir_and_ee_yml):
     """
     ee = """
     version: 3
+    images:
+      base_image:
+        name: quay.io/ansible/awx-ee:latest
     options:
         workdir: /yourmom
     """
@@ -463,6 +484,9 @@ def test_v3_no_workdir(cli, build_dir_and_ee_yml):
     """
     ee = """
     version: 3
+    images:
+      base_image:
+        name: quay.io/ansible/awx-ee:latest
     options:
         workdir:
     """
@@ -485,6 +509,9 @@ def test_v3_set_user_id(cli, build_dir_and_ee_yml):
     tmpdir, eeyml = build_dir_and_ee_yml(
         """
         version: 3
+        images:
+          base_image:
+            name: quay.io/ansible/awx-ee:latest
         options:
           user: bob
         """
diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py
index e3e8e4d..6137544 100644
--- a/test/unit/test_cli.py
+++ b/test/unit/test_cli.py
@@ -44,7 +44,7 @@ def test_custom_ansible_galaxy_cli_role_opts(exec_env_definition_file, tmp_path)
 
 
 def test_build_args_empty_value(exec_env_definition_file, tmp_path):
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
 
     aee = prepare(['build', '-f', path, '--build-arg', 'ANSIBLE_GALAXY_CLI_ROLE_OPTS=', '-c', str(tmp_path)])
@@ -52,7 +52,7 @@ def test_build_args_empty_value(exec_env_definition_file, tmp_path):
 
 
 def test_build_args_no_trailing_equal(exec_env_definition_file, tmp_path):
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
 
     aee = prepare(['build', '-f', path, '--build-arg', 'ANSIBLE_GALAXY_CLI_ROLE_OPTS', '-c', str(tmp_path)])
@@ -60,7 +60,7 @@ def test_build_args_no_trailing_equal(exec_env_definition_file, tmp_path):
 
 
 def test_build_args_multiple_equal_sign_value(exec_env_definition_file, tmp_path):
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
 
     aee = prepare(['build', '-f', path,
@@ -257,7 +257,7 @@ def test_squash_default(exec_env_definition_file, tmp_path):
     '''
     Test the squash CLI option with default.
     '''
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
     aee = prepare(['build',
                    '-f', path,
@@ -271,7 +271,7 @@ def test_squash_all(exec_env_definition_file, tmp_path):
     '''
     Test the squash CLI option with 'all'.
     '''
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
     aee = prepare(['build',
                    '-f', path,
@@ -286,7 +286,7 @@ def test_squash_off(exec_env_definition_file, tmp_path):
     '''
     Test the squash CLI option with 'off'.
     '''
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
     aee = prepare(['build',
                    '-f', path,
@@ -301,7 +301,7 @@ def test_squash_new(exec_env_definition_file, tmp_path):
     '''
     Test the squash CLI option with 'new'.
     '''
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
     aee = prepare(['build',
                    '-f', path,
@@ -317,7 +317,7 @@ def test_squash_ignored(exec_env_definition_file, tmp_path):
     '''
     Test the squash CLI option is ignored with docker.
     '''
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
     aee = prepare(['build',
                    '-f', path,
@@ -348,7 +348,7 @@ def test_as_executable_module(capsys):
                           ('--verbosity', 1),
                           ('--verbosity=2', 2)])
 def test_verbosity(exec_env_definition_file, tmp_path, verbosity_opt, expected_val):
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
 
     aee = prepare(['create',
@@ -361,7 +361,7 @@ def test_verbosity(exec_env_definition_file, tmp_path, verbosity_opt, expected_v
 
 @pytest.mark.parametrize('verbosity_opt', ['-v4', '-vvvv', '-v -v -v -v', '--verbosity=4'])
 def test_invalid_verbosity(exec_env_definition_file, tmp_path, verbosity_opt):
-    content = {'version': 3}
+    content = {'version': 3, 'images': {'base_image': {'name': 'base_image:latest'}}}
     path = str(exec_env_definition_file(content=content))
     with pytest.raises(ValueError, match=f'maximum verbosity is {constants.max_verbosity}'):
         prepare(['create', '-f', path, '-c', str(tmp_path), verbosity_opt])
diff --git a/test/unit/test_introspect.py b/test/unit/test_introspect.py
index 62dcc49..36ea823 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 8323b67..db21b9b 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
@@ -63,11 +64,13 @@ class TestUserDefinition:
             "Additional properties are not allowed ('prepend' was unexpected)"
         ),  # 'prepend' is renamed in v2
         (
-            "{'version': 3, 'additional_build_files': [ {'src': 'a', 'dest': '../b'} ]}",
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}}, "
+            "'additional_build_files': [ {'src': 'a', 'dest': '../b'} ]}",
             "'dest' must not be an absolute path or contain '..': ../b"
         ),  # destination cannot contain ..
         (
-            "{'version': 3, 'additional_build_files': [ {'src': 'a', 'dest': '/b'} ]}",
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}}, "
+            "'additional_build_files': [ {'src': 'a', 'dest': '/b'} ]}",
             "'dest' must not be an absolute path or contain '..': /b"
         ),  # destination cannot be absolute
         (
@@ -168,6 +171,7 @@ class TestUserDefinition:
         path = exec_env_definition_file(
             """
             {'version': 3,
+             'images': { 'base_image': {'name': 'base_image:latest'}},
              'dependencies': {
                 'ansible_core': {'package_pip': 'ansible-core==2.13'},
                 'ansible_runner': { 'package_pip': 'ansible-runner==2.3.1'}
@@ -186,7 +190,8 @@ class TestUserDefinition:
         Test that inline values for dependencies.python work.
         """
         path = exec_env_definition_file(
-            "{'version': 3, 'dependencies': {'python': ['req1', 'req2']}}"
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}},"
+            "'dependencies': {'python': ['req1', 'req2']}}"
         )
         definition = UserDefinition(path)
         definition.validate()
@@ -199,7 +204,8 @@ class TestUserDefinition:
         Test that inline values for dependencies.system work.
         """
         path = exec_env_definition_file(
-            "{'version': 3, 'dependencies': {'system': ['req1', 'req2']}}"
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}},"
+            "'dependencies': {'system': ['req1', 'req2']}}"
         )
         definition = UserDefinition(path)
         definition.validate()
@@ -212,7 +218,7 @@ class TestUserDefinition:
         Test that options.skip_ansible_check defaults to False
         """
         path = exec_env_definition_file(
-            "{'version': 3}"
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}}}"
         )
         definition = UserDefinition(path)
         definition.validate()
@@ -225,7 +231,7 @@ class TestUserDefinition:
         Test that options.user defaults to 1000
         """
         path = exec_env_definition_file(
-            "{'version': 3}"
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}}}"
         )
         definition = UserDefinition(path)
         definition.validate()
@@ -238,7 +244,8 @@ class TestUserDefinition:
         Test that options.user sets to username
         """
         path = exec_env_definition_file(
-            "{'version': 3, 'options': {'user': 'bob'}}"
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}},"
+            "'options': {'user': 'bob'}}"
         )
         definition = UserDefinition(path)
         definition.validate()
@@ -251,7 +258,8 @@ class TestUserDefinition:
         Test that options.tags sets to tags
         """
         path = exec_env_definition_file(
-            "{'version': 3, 'options': {'tags': ['ee_test:latest']}}"
+            "{'version': 3, 'images': { 'base_image': {'name': 'base_image:latest'}}, "
+            "'options': {'tags': ['ee_test:latest']}}"
         )
         definition = UserDefinition(path)
         definition.validate()
@@ -299,3 +307,35 @@ class TestImageDescription:
         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()