diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d79885bc72c3516de368c054cae7fbe162c74010..1a6c751b60256453385612cc831493f02d785e12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,6 +13,9 @@ linting: tests: stage: test + variables: + NORTH_DOCKER_URL: ${CI_TARGET_DOCKER_ENV_URL} + NORTH_DOCKER_NAME_PREFIX: north-${CI_COMMIT_REF_NAME} script: - pip install -e . - python -m pytest --cov=north -sv tests diff --git a/README.md b/README.md index 407fe385f993ecb8a60952fac91710a9d4d5c33a..f4410bbbb2487f1369f26eb5cfbf6868ce66fc7b 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,15 @@ We recomment using vs-code. Here are vs-code settings that match the CI/CD linti - `tests` - The [pytest](https://docs.pytest.org/) tests - `setup.py` - Install the package with pip - `docker` - All the docker files, scripts for creating/managing images, documentation + + +## Architecture + +North is supposed to be run stand alone or as part of a NOMAD Oasis. Currently, we are +only targeting docker as the environment to run tools. + + + +- all containers that are run need to have an explicit name and this name has to be prefixed +with `north.config.docker_name_prefix`. This will allow us to target docker environments +that are used by other services. diff --git a/docs/assets/north-oasis-architecture.png b/docs/assets/north-oasis-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..0af7b00476ac70cc62dd86c8731d116cedd39ca4 Binary files /dev/null and b/docs/assets/north-oasis-architecture.png differ diff --git a/north/config.py b/north/config.py index c4d22eba8f3445d9b11f1bba1da64703cbb61b42..b9ab171582fd3a9a3b21bf25ab197dfd05a2d377 100644 --- a/north/config.py +++ b/north/config.py @@ -21,7 +21,7 @@ This config file is based on pydantic's [settings management](https://pydantic-docs.helpmanual.io/usage/settings/). ''' -from typing import Dict, Any +from typing import Dict, Any, Optional from pydantic import Field, BaseSettings import yaml import os.path @@ -29,6 +29,19 @@ import os class NorthConfig(BaseSettings): + docker_url: Optional[str] = Field( + None, + description=( + 'The URL to remotely (or locally) connect to the docker engine API. ' + 'If this is not given, the docker config will be read from the local env.')) + + docker_name_prefix: str = Field( + 'north', + description=( + 'A prefix used for all container names. This can be used to avoid collisions ' + 'with other services using the same docker environment.') + ) + secret: str = Field( 'this is a secret', description='The secret for generating JWT tokens and other cryptographic material.') diff --git a/requirements.txt b/requirements.txt index d163dab93a43d0f0216a2fab132ea5bc812d9e67..4fca4c4feab7c21db14d7545bbf70176bc15d103 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ pytest-cov types-PyYAML types-requests uvicorn +docker diff --git a/tests/test_docker.py b/tests/test_docker.py new file mode 100644 index 0000000000000000000000000000000000000000..b15c9dae0bfd25856c103746862cdec644453b40 --- /dev/null +++ b/tests/test_docker.py @@ -0,0 +1,59 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import pytest +import docker +from docker import DockerClient + +from north import config + + +@pytest.fixture(scope='session') +def docker_client() -> DockerClient: + ''' + A session fixture for using docker. It will try to remove all left-over containers + after the test session. + ''' + if config.docker_url: + docker_client = DockerClient(base_url=config.docker_url) + else: + docker_client = docker.from_env() + + yield docker_client + + # Remove old containrs that might be leftover from failing tests + docker_name_prefix_filter = dict(filters=dict(name=f'{config.docker_name_prefix}-.*')) + for container in docker_client.containers.list(**docker_name_prefix_filter): + container.stop() + for container in docker_client.containers.list(**docker_name_prefix_filter, all=True): + container.remove() + + +def assert_container(docker_client: DockerClient, name: str, remove: bool = False): + container = docker_client.containers.get(name) + assert container is not None + if remove: + container.remove() + + +def test_run_hello(docker_client: DockerClient): + name = f'{config.docker_name_prefix}-test-hello-world' + results = docker_client.containers.run('ubuntu:latest', 'echo hello world', name=name) + + assert results == b'hello world\n', results + assert_container(docker_client, name, remove=True)