Repository: exasol/integration-test-docker-environment (language: python)

Issue:
Detect Docker flavor
Currently ITDE uses `docker_exec` to access the Docker Container, e.g. to analyze the content of some logfiles.  With version 8 and higher the format of the Docker Containers might change so that `docker_exec` is no longer possible. Instead ITDE will then need to use SSH access.

The current ticket therefore requests to detect the version number of the Exasol database inside the Docker Container and to decide whether to use `docker_exec` or SSH access.

This could be implemented for example by creating a new luigi task.

AC
1. IDTE detects the version of the Exasol database in the Docker Container and makes the result available to method `register_required()`.
Upgrade to Exasol 8.0 prerelease
When Exasol 8.0 is available on docker hub, we need to upgrade ITDE to use the new release in order to verify everything works with the new release.

## Acceptance criteria
* [ ] All tests are green
Upgrade to Exasol 8.0 prerelease
When Exasol 8.0 is available on docker hub, we need to upgrade ITDE to use the new release in order to verify everything works with the new release.

## Acceptance criteria
* [ ] All tests are green


Discussion / hints:
Discussion with @tkilias: 
We currently cannot predict the precise version number that will indicate that SSH access is required in future.
Therefore we will plan to add another individual parameter (cli- or api) to switch between `docker_exec` and SSH access.
By this we can use SSH access with arbitrary docker images.

We plan to inject file  `/root/.ssh/authorized_keys` as separate docker volume for directory `/root/.ssh/`.



Running ITDE fails when using docker image `exasol/docker-db:prerelease-8.17.0`:

```
Status: Downloaded newer image for exasol/script-language-container:integration-test-docker-environment-runner-f9698db94e5a533c3b50ea44beffc1aa6c33c291
docker.io/exasol/script-language-container:integration-test-docker-environment-runner-f9698db94e5a533c3b50ea44beffc1aa6c33c291
ERROR - SpawnTestEnvironmentWithDockerDB_4d932b7e4e(job_id=2023_05_11_11_34_27_SpawnTestEnvironmentWithDockerDB, no_cache=False, db_user=sys, create_certificates=False, test_container_content=null, additional_db_parameter=[], environment_name=test, docker_db_image_name=exasol/docker-db, docker_db_image_version=prerelease-8.17.0): Exception in run: invalid literal for int() with base 10: 'prerelease-8'
Traceback (most recent call last):
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/base/base_task.py", line 241, in run
    yield from task_generator
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 39, in run_task
    test_environment_info = yield from self._attempt_database_start()
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 49, in _attempt_database_start
    yield from self._start_database(attempt)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 147, in _start_database
    yield from self._spawn_database_and_test_container(network_info, ssl_volume_info, attempt)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 173, in _spawn_database_and_test_container
    DATABASE: self.create_spawn_database_task(network_info, certificate_volume_info, attempt)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_environment_with_docker_db.py", line 71, in create_spawn_database_task
    self.create_child_task_with_common_params(
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/base/base_task.py", line 336, in create_child_task_with_common_params
    return task_class(**params)
  File "/usr/local/lib/python3.8/dist-packages/luigi/task_register.py", line 98, in __call__
    h[k] = instantiate()
  File "/usr/local/lib/python3.8/dist-packages/luigi/task_register.py", line 79, in instantiate
    return super(Register, cls).__call__(*args, **kwargs)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_database.py", line 50, in __init__
    self.db_version = DbVersion.from_db_version_str(self.docker_db_image_version)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/db_version.py", line 21, in from_db_version_str
    version = tuple([int(v) for v in db_version.split(".")])
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/db_version.py", line 21, in <listcomp>
    version = tuple([int(v) for v in db_version.split(".")])
ValueError: invalid literal for int() with base 10: 'prerelease-8'
ERROR - [pid 21] Worker Worker(salt=183057980, workers=5, host=fv-az587-681, username=root, pid=12) failed    SpawnTestEnvironmentWithDockerDB_4d932b7e4e(job_id=2023_05_11_11_34_27_SpawnTestEnvironmentWithDockerDB, no_cache=False, db_user=sys, create_certificates=False, test_container_content=null, additional_db_parameter=[], environment_name=test, docker_db_image_name=exasol/docker-db, docker_db_image_version=prerelease-8.17.0)
```
Running ITDE fails when using docker image `exasol/docker-db:prerelease-8.17.0`:

```
Status: Downloaded newer image for exasol/script-language-container:integration-test-docker-environment-runner-f9698db94e5a533c3b50ea44beffc1aa6c33c291
docker.io/exasol/script-language-container:integration-test-docker-environment-runner-f9698db94e5a533c3b50ea44beffc1aa6c33c291
ERROR - SpawnTestEnvironmentWithDockerDB_4d932b7e4e(job_id=2023_05_11_11_34_27_SpawnTestEnvironmentWithDockerDB, no_cache=False, db_user=sys, create_certificates=False, test_container_content=null, additional_db_parameter=[], environment_name=test, docker_db_image_name=exasol/docker-db, docker_db_image_version=prerelease-8.17.0): Exception in run: invalid literal for int() with base 10: 'prerelease-8'
Traceback (most recent call last):
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/base/base_task.py", line 241, in run
    yield from task_generator
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 39, in run_task
    test_environment_info = yield from self._attempt_database_start()
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 49, in _attempt_database_start
    yield from self._start_database(attempt)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 147, in _start_database
    yield from self._spawn_database_and_test_container(network_info, ssl_volume_info, attempt)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/abstract_spawn_test_environment.py", line 173, in _spawn_database_and_test_container
    DATABASE: self.create_spawn_database_task(network_info, certificate_volume_info, attempt)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_environment_with_docker_db.py", line 71, in create_spawn_database_task
    self.create_child_task_with_common_params(
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/base/base_task.py", line 336, in create_child_task_with_common_params
    return task_class(**params)
  File "/usr/local/lib/python3.8/dist-packages/luigi/task_register.py", line 98, in __call__
    h[k] = instantiate()
  File "/usr/local/lib/python3.8/dist-packages/luigi/task_register.py", line 79, in instantiate
    return super(Register, cls).__call__(*args, **kwargs)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_database.py", line 50, in __init__
    self.db_version = DbVersion.from_db_version_str(self.docker_db_image_version)
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/db_version.py", line 21, in from_db_version_str
    version = tuple([int(v) for v in db_version.split(".")])
  File "/integration_test_docker_environment/exasol_integration_test_docker_environment/lib/test_environment/db_version.py", line 21, in <listcomp>
    version = tuple([int(v) for v in db_version.split(".")])
ValueError: invalid literal for int() with base 10: 'prerelease-8'
ERROR - [pid 21] Worker Worker(salt=183057980, workers=5, host=fv-az587-681, username=root, pid=12) failed    SpawnTestEnvironmentWithDockerDB_4d932b7e4e(job_id=2023_05_11_11_34_27_SpawnTestEnvironmentWithDockerDB, no_cache=False, db_user=sys, create_certificates=False, test_container_content=null, additional_db_parameter=[], environment_name=test, docker_db_image_name=exasol/docker-db, docker_db_image_version=prerelease-8.17.0)
```

Fix patch:
diff --git a/doc/changes/changes_1.7.0.md b/doc/changes/changes_1.7.0.md
index fb5846cde..fad7a5286 100644
--- a/doc/changes/changes_1.7.0.md
+++ b/doc/changes/changes_1.7.0.md
@@ -5,6 +5,7 @@
 Up to version 1.6.0 ITDE used `docker_exec` to access the Docker Container, e.g. to analyze the content of logfiles or execute some shell commands. With version 8 of Exasol database the format of the Docker Containers might change so that `docker_exec` is no longer possible. Instead ITDE will then need to use SSH access.
 
 The current release therefore enhances ITDE to enable to access the Docker Container via SSH.
+The user can select the docker access method with command line option `--db-os-access`, see User Guide.
 
 T.B.D.
 
@@ -19,5 +20,7 @@ If you need further versions, please open an issue.
 
 ## Changes
 
+* #301: Added commandline option `--db-os-access`
 * #302: Added support to create an SSH key for accessing Docker Container
 * #241: Renamed environment variable for test execution from `GOOGLE_CLOUD_BUILD` to `RUN_SLC_TESTS_WITHIN_CONTAINER`
+* #190: Added support for the Exasol 8.0 Docker-DB prerelease version
diff --git a/doc/developer_guide/developer_guide.rst b/doc/developer_guide/developer_guide.rst
index 6413631c1..806c15dd6 100644
--- a/doc/developer_guide/developer_guide.rst
+++ b/doc/developer_guide/developer_guide.rst
@@ -79,4 +79,23 @@ One of the release steps failed (Partial Release)
 
     **Scenario**: Publishing of the release on Github was successfully but during the PyPi release, the upload step got interrupted.
 
-    **Solution**: Manually push the package to PyPi
\ No newline at end of file
+    **Solution**: Manually push the package to PyPi
+
+Running Tests
+*************
+
+You can execute all tests in a single file with the following command:
+
+.. code-block:: shell
+
+  poetry run python exasol_integration_test_docker_environment/test/test_cli_test_environment_db_mem_size.py
+
+Some tests will use prebuilt Docker Containers.
+After changing the implementation you might need to rebuild the container in order to make
+your changes effective when executing the tests. 
+
+Please use the following command to rebuild the Docker Container:
+
+.. code-block:: shell
+
+  starter_scripts/build_docker_runner_image.sh
diff --git a/doc/user_guide/user_guide.rst b/doc/user_guide/user_guide.rst
index 52effbf1c..853894129 100644
--- a/doc/user_guide/user_guide.rst
+++ b/doc/user_guide/user_guide.rst
@@ -135,6 +135,13 @@ The following options are available to customize the test environment.
      --docker-db-image-name TEXT     Docker DB Image Name against which the tests
                                      should run.  [default: exasol/docker-db]
 
+     --db-os-access METHOD           How to access file system and command
+                                     line of the database operating
+                                     system. Experimental option, will show no
+                                     effect until implementation of feature
+                                     SSH access is completed. [default:
+                                     DOCKER_EXEC]
+
      --output-directory DIRECTORY    Output directory where the system stores all
                                      output and log files.  [default:
                                      .build_output]


Test patch:
diff --git a/exasol_integration_test_docker_environment/cli/cli.py b/exasol_integration_test_docker_environment/cli/cli.py
index e92ef9cf5..5806cd2da 100644
--- a/exasol_integration_test_docker_environment/cli/cli.py
+++ b/exasol_integration_test_docker_environment/cli/cli.py
@@ -14,7 +14,7 @@ def cli():
 
             $ itde health
 
-        Spawn a itde test environment:
+        Spawn an ITDE test environment:
 
             $ itde spawn-test-environment --environment-name test \\
             --database-port-forward 8888 --bucketfs-port-forward 6666 \\
diff --git a/exasol_integration_test_docker_environment/cli/commands/spawn_test_environment.py b/exasol_integration_test_docker_environment/cli/commands/spawn_test_environment.py
index 4d3d02e98..5fa89a3b8 100644
--- a/exasol_integration_test_docker_environment/cli/commands/spawn_test_environment.py
+++ b/exasol_integration_test_docker_environment/cli/commands/spawn_test_environment.py
@@ -89,6 +89,7 @@ def spawn_test_environment(
         docker_runtime: Optional[str],
         docker_db_image_version: str,
         docker_db_image_name: str,
+        db_os_access: Optional[str],
         create_certificates: bool,
         additional_db_parameter: Tuple[str, ...],
         source_docker_repository_name: str,
@@ -107,7 +108,7 @@ def spawn_test_environment(
         use_job_specific_log_file: bool
 ):
     """
-    This command spawn a test environment with a docker-db container and a connected test-container.
+    This command spawns a test environment with a docker-db container and a connected test-container.
     The test-container is reachable by the database for output redirects of UDFs.
     """
     with TerminationHandler():
@@ -122,6 +123,7 @@ def spawn_test_environment(
                 docker_runtime,
                 docker_db_image_version,
                 docker_db_image_name,
+                db_os_access,
                 create_certificates,
                 additional_db_parameter,
                 source_docker_repository_name,
diff --git a/exasol_integration_test_docker_environment/cli/options/test_environment_options.py b/exasol_integration_test_docker_environment/cli/options/test_environment_options.py
index f05122c26..9c4c43c24 100644
--- a/exasol_integration_test_docker_environment/cli/options/test_environment_options.py
+++ b/exasol_integration_test_docker_environment/cli/options/test_environment_options.py
@@ -1,4 +1,8 @@
 import click
+from exasol_integration_test_docker_environment \
+    .lib.test_environment.parameter \
+    .docker_db_test_environment_parameter \
+    import DbOsAccess
 
 test_environment_options = [
     click.option('--environment-type', type=click.Choice(['docker_db', 'external_db']), default="""docker_db""",
@@ -19,6 +23,12 @@
     click.option('--docker-db-image-name', type=str, default="""exasol/docker-db""",
                  show_default=True,
                  help="""Docker DB Image Name against which the tests should run."""),
+    click.option('--db-os-access', type=click.Choice([e.name for e in DbOsAccess]),
+                 metavar="METHOD", default="""DOCKER_EXEC""", show_default=True,
+                 help="""How to access file system and command line of the
+     		 database operating system. Experimental option, will show no
+     		 effect until implementation of feature SSH access is
+     		 completed."""),
     click.option('--create-certificates/--no-create-certificates', default=False,
                  help="""Creates and injects SSL certificates to the Docker DB container."""),
     click.option('--additional-db-parameter', '-p', type=str, multiple=True,
diff --git a/exasol_integration_test_docker_environment/lib/api/spawn_test_environment.py b/exasol_integration_test_docker_environment/lib/api/spawn_test_environment.py
index a0b5182c8..448bae7d1 100644
--- a/exasol_integration_test_docker_environment/lib/api/spawn_test_environment.py
+++ b/exasol_integration_test_docker_environment/lib/api/spawn_test_environment.py
@@ -2,8 +2,13 @@
 from typing import Tuple, Optional, Callable
 import humanfriendly
 
-from exasol_integration_test_docker_environment.lib.api.common import set_build_config, set_docker_repository_config, \
-    run_task, generate_root_task, cli_function
+from exasol_integration_test_docker_environment.lib.api.common import (
+    set_build_config,
+    set_docker_repository_config,
+    run_task,
+    generate_root_task,
+    cli_function,
+)
 from exasol_integration_test_docker_environment.cli.options.docker_repository_options import \
     DEFAULT_DOCKER_REPOSITORY_NAME
 from exasol_integration_test_docker_environment.cli.options.system_options import DEFAULT_OUTPUT_DIRECTORY
@@ -15,6 +20,9 @@
 from exasol_integration_test_docker_environment.lib.docker.networks.utils import remove_docker_networks
 from exasol_integration_test_docker_environment.lib.test_environment.spawn_test_environment_with_docker_db import \
     SpawnTestEnvironmentWithDockerDB
+from exasol_integration_test_docker_environment \
+    .lib.test_environment.parameter \
+    .docker_db_test_environment_parameter import DbOsAccess
 
 
 def _cleanup(environment_info: EnvironmentInfo) -> None:
@@ -34,6 +42,7 @@ def spawn_test_environment(
         docker_runtime: Optional[str] = None,
         docker_db_image_version: str = LATEST_DB_VERSION,
         docker_db_image_name: str = "exasol/docker-db",
+        db_os_access: Optional[str] = "DOCKER_EXEC",
         create_certificates: bool = False,
         additional_db_parameter: Tuple[str, ...] = tuple(),
         source_docker_repository_name: str = DEFAULT_DOCKER_REPOSITORY_NAME,
@@ -89,6 +98,7 @@ def spawn_test_environment(
                                               docker_runtime=docker_runtime,
                                               docker_db_image_version=docker_db_image_version,
                                               docker_db_image_name=docker_db_image_name,
+                                              db_os_access=DbOsAccess[db_os_access],
                                               db_user="sys",
                                               db_password="exasol",
                                               bucketfs_write_password="write",
diff --git a/exasol_integration_test_docker_environment/lib/api/spawn_test_environment_with_test_container.py b/exasol_integration_test_docker_environment/lib/api/spawn_test_environment_with_test_container.py
index ec9918cc0..9fec16988 100644
--- a/exasol_integration_test_docker_environment/lib/api/spawn_test_environment_with_test_container.py
+++ b/exasol_integration_test_docker_environment/lib/api/spawn_test_environment_with_test_container.py
@@ -17,6 +17,9 @@
 from exasol_integration_test_docker_environment.lib.docker.networks.utils import remove_docker_networks
 from exasol_integration_test_docker_environment.lib.test_environment.spawn_test_environment_with_docker_db import \
     SpawnTestEnvironmentWithDockerDB
+from exasol_integration_test_docker_environment \
+    .lib.test_environment.parameter \
+    .docker_db_test_environment_parameter import DbOsAccess
 
 
 def _cleanup(environment_info: EnvironmentInfo) -> None:
@@ -41,6 +44,7 @@ def spawn_test_environment_with_test_container(
         docker_runtime: Optional[str] = None,
         docker_db_image_version: str = LATEST_DB_VERSION,
         docker_db_image_name: str = "exasol/docker-db",
+        db_os_access: str = "DOCKER_EXEC",
         create_certificates: bool = False,
         additional_db_parameter: Tuple[str, ...] = tuple(),
         source_docker_repository_name: str = DEFAULT_DOCKER_REPOSITORY_NAME,
@@ -97,6 +101,7 @@ def spawn_test_environment_with_test_container(
                                               docker_runtime=docker_runtime,
                                               docker_db_image_version=docker_db_image_version,
                                               docker_db_image_name=docker_db_image_name,
+                                              db_os_access=DbOsAccess[db_os_access],
                                               db_user="sys",
                                               db_password="exasol",
                                               bucketfs_write_password="write",
diff --git a/exasol_integration_test_docker_environment/lib/base/ssh_access.py b/exasol_integration_test_docker_environment/lib/base/ssh_access.py
index 6c429b621..c9d0abb07 100644
--- a/exasol_integration_test_docker_environment/lib/base/ssh_access.py
+++ b/exasol_integration_test_docker_environment/lib/base/ssh_access.py
@@ -83,7 +83,7 @@ def default_folder(cls) -> Path:
         return folder
 
     @classmethod
-    def from_folder(cls, folder: Optional[Path] = None, args=None) -> 'SshKey':
+    def from_folder(cls, folder: Optional[Path] = None) -> 'SshKey':
         folder = folder if folder else cls.default_folder()
         priv = folder / "id_rsa"
         pub = folder / "id_rsa.pub"
diff --git a/exasol_integration_test_docker_environment/lib/test_environment/parameter/docker_db_test_environment_parameter.py b/exasol_integration_test_docker_environment/lib/test_environment/parameter/docker_db_test_environment_parameter.py
index be131e0d4..3e7efd815 100644
--- a/exasol_integration_test_docker_environment/lib/test_environment/parameter/docker_db_test_environment_parameter.py
+++ b/exasol_integration_test_docker_environment/lib/test_environment/parameter/docker_db_test_environment_parameter.py
@@ -1,11 +1,28 @@
 import luigi
+from enum import Enum, auto
 from luigi import Config
 
+class DbOsAccess(Enum):
+    """
+    This enum represents different methods to access the operating system
+    of the Docker Container running the Exasol database.
+
+    The Docker Container of older database versions allowed to use
+    ``docker_exec`` as well, while newer versions require SSH.
+
+    Currently the SSH access is not implemented fully but the current test
+    case will verify that when using SSH access a file with the required
+    private key is generated.
+    """
+    DOCKER_EXEC = auto()
+    SSH = auto()
+
 
 class DockerDBTestEnvironmentParameter(Config):
     docker_db_image_name = luigi.OptionalParameter(None)
     docker_db_image_version = luigi.OptionalParameter(None)
     reuse_database = luigi.BoolParameter(False, significant=False)
+    db_os_access = luigi.EnumParameter(DbOsAccess.DOCKER_EXEC, enum=DbOsAccess, significant=False)
     no_database_cleanup_after_success = luigi.BoolParameter(False, significant=False)
     no_database_cleanup_after_failure = luigi.BoolParameter(False, significant=False)
     database_port_forward = luigi.OptionalParameter(None, significant=False)
diff --git a/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_database.py b/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_database.py
index c723ed227..392c1e025 100644
--- a/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_database.py
+++ b/exasol_integration_test_docker_environment/lib/test_environment/spawn_test_database.py
@@ -21,8 +21,14 @@
 from exasol_integration_test_docker_environment.lib.docker.images.image_info import ImageInfo
 from exasol_integration_test_docker_environment.lib.test_environment.db_version import DbVersion
 from exasol_integration_test_docker_environment.lib.test_environment.docker_container_copy import DockerContainerCopy
-from exasol_integration_test_docker_environment.lib.test_environment.parameter.docker_db_test_environment_parameter import \
-    DockerDBTestEnvironmentParameter
+from exasol_integration_test_docker_environment.lib \
+    .test_environment.parameter \
+    .docker_db_test_environment_parameter import (
+        DbOsAccess,
+        DockerDBTestEnvironmentParameter,
+)
+from exasol_integration_test_docker_environment.lib.base.ssh_access import SshKey
+
 
 BUCKETFS_PORT = "6583"
 DB_PORT = "8888"
@@ -99,6 +105,8 @@ def _create_database_container(self, db_ip_address: str, db_private_network: str
             if self.certificate_volume_name is not None:
                 volumes[self.certificate_volume_name] = {"bind": CERTIFICATES_MOUNT_DIR, "mode": "ro"}
 
+            if self.db_os_access == DbOsAccess.SSH:
+                sshkey = SshKey.from_folder()
             db_container = \
                 docker_client.containers.create(
                     image="%s" % (docker_db_image_info.get_source_complete_name()),
diff --git a/exasol_integration_test_docker_environment/test/conftest.py b/exasol_integration_test_docker_environment/test/conftest.py
new file mode 100644
index 000000000..65c2f8b20
--- /dev/null
+++ b/exasol_integration_test_docker_environment/test/conftest.py
@@ -0,0 +1,56 @@
+import contextlib
+import logging
+import pytest
+from typing import Callable, Iterator, List, Optional
+
+from exasol_integration_test_docker_environment.testing import utils
+from exasol_integration_test_docker_environment.testing \
+   .exaslct_test_environment import (
+       ExaslctTestEnvironment,
+       SpawnedTestEnvironments,
+   )
+
+
+@pytest.fixture
+def cli_isolation(request) -> Iterator[ExaslctTestEnvironment]:
+    testname = request.node.name
+    environment = ExaslctTestEnvironment(
+        test_object=None,
+        executable="itde",
+        clean_images_at_close=True,
+        name=testname,
+    )
+    try:
+        yield environment
+    finally:
+        utils.close_environments(environment)
+
+
+@pytest.fixture
+def database(cli_isolation) -> Callable:
+    """
+    Returns a method that test case implementations can use to create a
+    context with a database.
+
+    The test case optionally can pass a name and additional parameters for
+    spawning the database:
+
+    def test_case(database):
+        with database(additional_parameters = ["--option"]):
+            ...
+    """
+    @contextlib.contextmanager
+    def create_context(
+            name: Optional[str] = None,
+            additional_parameters: Optional[List[str]] = None,
+    ):
+        name = name if name else cli_isolation.name
+        spawned = cli_isolation.spawn_docker_test_environments(
+            name=name,
+            additional_parameter=additional_parameters,
+        )
+        try:
+            yield spawned
+        finally:
+            utils.close_environments(spawned)
+    return create_context
diff --git a/exasol_integration_test_docker_environment/test/test_click_api_consistency.py b/exasol_integration_test_docker_environment/test/test_click_api_consistency.py
index 6ee38277c..3fb2d185d 100644
--- a/exasol_integration_test_docker_environment/test/test_click_api_consistency.py
+++ b/exasol_integration_test_docker_environment/test/test_click_api_consistency.py
@@ -27,9 +27,15 @@ def test_api_arguments(self):
             if "return" in api_spec.annotations:
                 del api_spec.annotations["return"]
 
-            multiassert([lambda: self.assertEqual(api_spec.args, cli_spec.args),
-                         lambda: self.assertEqual(api_spec.annotations, cli_spec.annotations),
-                         lambda: self.assertEqual(api_spec.args, param_names_of_click_call(cli_call))], self)
+            loop = f"cli: {cli_call}, api: {api_call}"
+            def cli_spec_names():
+                self.assertEqual(api_spec.args, cli_spec.args, msg=f"{loop} - cli spec")
+            def type_annotations():
+                self.assertEqual(api_spec.annotations, cli_spec.annotations, msg=f"{loop} - type annotations")
+            def cli_param_names():
+                self.assertEqual(api_spec.args, param_names_of_click_call(cli_call), msg=f"{loop} - cli param names")
+
+            multiassert([cli_spec_names, type_annotations, cli_param_names], self)
 
     def test_api_default_values(self):
         """
diff --git a/exasol_integration_test_docker_environment/test/test_docker_access_method.py b/exasol_integration_test_docker_environment/test/test_docker_access_method.py
new file mode 100644
index 000000000..b6adb7a89
--- /dev/null
+++ b/exasol_integration_test_docker_environment/test/test_docker_access_method.py
@@ -0,0 +1,38 @@
+import luigi
+import pytest
+
+from exasol_integration_test_docker_environment.lib.api.common import generate_root_task
+from exasol_integration_test_docker_environment.lib.base.dependency_logger_base_task \
+     import DependencyLoggerBaseTask
+from exasol_integration_test_docker_environment.lib.test_environment.parameter.docker_db_test_environment_parameter \
+     import DbOsAccess, DockerDBTestEnvironmentParameter
+
+
+class Testee(DependencyLoggerBaseTask, DockerDBTestEnvironmentParameter):
+    def run_task(self):
+        pass
+
+    @classmethod
+    def make(cls, method: str) -> 'Testee':
+        kwargs={"task_class": Testee}
+        if method:
+            kwargs["db_os_access"] = method
+        task = generate_root_task(**kwargs)
+        luigi.build([task], workers=1, local_scheduler=True, log_level="INFO")
+        return task
+
+
+def test_db_os_access_default():
+    testee = Testee.make(None)
+    assert testee.db_os_access == DbOsAccess.DOCKER_EXEC
+
+
+@pytest.mark.parametrize("method", [DbOsAccess.DOCKER_EXEC, DbOsAccess.SSH])
+def test_db_os_access_parameter(method):
+    testee = Testee.make(method)
+    assert testee.db_os_access == method
+
+
+def test_db_os_access_invalid():
+    with pytest.raises(AttributeError) as ex:
+        Testee.make("invalid method")
diff --git a/exasol_integration_test_docker_environment/test/test_ssh_access.py b/exasol_integration_test_docker_environment/test/test_ssh_access.py
new file mode 100644
index 000000000..f902c4282
--- /dev/null
+++ b/exasol_integration_test_docker_environment/test/test_ssh_access.py
@@ -0,0 +1,11 @@
+import pytest
+
+from exasol_integration_test_docker_environment.lib.base.ssh_access import SshKey
+
+
+def test_generate_ssh_key_file(database):
+    with database(
+            additional_parameters = ["--db-os-access", "SSH"],
+    ):
+        file = SshKey.default_folder() / "id_rsa"
+    assert file.exists()
diff --git a/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py b/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py
index bb0f7ce06..94ba39ce9 100644
--- a/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py
+++ b/exasol_integration_test_docker_environment/testing/exaslct_test_environment.py
@@ -26,18 +26,24 @@ def _cleanup(env_name: str):
     remove_docker_volumes([f"db_container_{env_name}_volume"])
 
 
+def get_class(test_object):
+    if test_object is None:
+        return None
+    if not inspect.isclass(test_object):
+        # test_object is an instance -> return its class
+        return  test_object.__class__
+    return test_object
+
+
 class ExaslctTestEnvironment:
 
-    def __init__(self, test_object, executable="./exaslct", clean_images_at_close=True):
+    def __init__(self, test_object, executable="./exaslct", clean_images_at_close=True, name=None):
         self.clean_images_at_close = clean_images_at_close
         self.executable = executable
         self.test_object = test_object
-        if not inspect.isclass(self.test_object):
-            self.test_class = self.test_object.__class__
-        else:
-            self.test_class = self.test_object
+        self.test_class = get_class(test_object)
         self.flavor_path = self.get_test_flavor()
-        self.name = self.test_class.__name__
+        self.name = name if name else self.test_class.__name__
         self._docker_repository_name = default_docker_repository_name(self.name)
         if "RUN_SLC_TESTS_WITHIN_CONTAINER" in os.environ:
             # We need to put the output directories into the workdir,
@@ -52,6 +58,8 @@ def __init__(self, test_object, executable="./exaslct", clean_images_at_close=Tr
         self._update_attributes()
 
     def get_test_flavor(self):
+        if self.test_class is None:
+            return None
         source_file_of_test_object = inspect.getsourcefile(self.test_class)
         flavor_path = Path(os.path.realpath(source_file_of_test_object)).parent.joinpath(
             "resources/test-flavor")
@@ -132,16 +140,18 @@ def spawn_docker_test_environments(self, name: str, additional_parameter: List[s
             bucketfs_password="write",
             database_port=database_port,
             bucketfs_port=bucketfs_port)
-        docker_db_version_parameter = ""
-        db_version_from_env = check_db_version_from_env()
-        if db_version_from_env is not None:
-            docker_db_version_parameter = f'--docker-db-image-version "{db_version_from_env}"'
-        if additional_parameter is None:
-            additional_parameter = []
-        arguments = " ".join([f"--environment-name {on_host_parameter.name}",
-                              f"--database-port-forward {on_host_parameter.database_port}",
-                              f"--bucketfs-port-forward {on_host_parameter.bucketfs_port}",
-                              docker_db_version_parameter] + additional_parameter)
+
+        arguments = [
+            f"--environment-name {on_host_parameter.name}",
+            f"--database-port-forward {on_host_parameter.database_port}",
+            f"--bucketfs-port-forward {on_host_parameter.bucketfs_port}",
+        ]
+        db_version = check_db_version_from_env()
+        if db_version:
+            arguments.append(f'--docker-db-image-version "{db_version}"')
+        if additional_parameter:
+            arguments += additional_parameter
+        arguments = " ".join(arguments)
 
         command = f"{self.executable} spawn-test-environment {arguments}"
         completed_process = self.run_command(command, use_flavor_path=False, use_docker_repository=False,