Docker-Services-CLI 0.12.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docker_services_cli/__init__.py +38 -0
- docker_services_cli/cli.py +196 -0
- docker_services_cli/config.py +171 -0
- docker_services_cli/docker-services.yml +86 -0
- docker_services_cli/env.py +194 -0
- docker_services_cli/services.py +228 -0
- docker_services_cli-0.12.2.dist-info/METADATA +37 -0
- docker_services_cli-0.12.2.dist-info/RECORD +12 -0
- docker_services_cli-0.12.2.dist-info/WHEEL +4 -0
- docker_services_cli-0.12.2.dist-info/entry_points.txt +2 -0
- docker_services_cli-0.12.2.dist-info/licenses/AUTHORS.rst +9 -0
- docker_services_cli-0.12.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2020-2025 CERN.
|
|
2
|
+
# SPDX-FileCopyrightText: 2022 University Münster.
|
|
3
|
+
# SPDX-FileCopyrightText: 2022-2026 TU Wien.
|
|
4
|
+
# SPDX-FileCopyrightText: 2023 Graz University of Technology.
|
|
5
|
+
# SPDX-License-Identifier: MIT
|
|
6
|
+
|
|
7
|
+
"""Module to ease the creation and management of services.
|
|
8
|
+
|
|
9
|
+
The specific version for the services can be set through environment variables
|
|
10
|
+
|
|
11
|
+
.. code-block:: console
|
|
12
|
+
|
|
13
|
+
$ export OPENSEARCH_VERSION=2.16.0
|
|
14
|
+
|
|
15
|
+
It can also use the centrally managed (supported) major version:
|
|
16
|
+
|
|
17
|
+
.. code-block:: console
|
|
18
|
+
|
|
19
|
+
$ export OPENSEARCH_VERSION=OPENSEARCH_2_LATEST
|
|
20
|
+
|
|
21
|
+
Then it simply needs to boot up the services. Note that if no version was
|
|
22
|
+
exported in the environment, the CLI will use the default values set in
|
|
23
|
+
``env.py``.
|
|
24
|
+
|
|
25
|
+
.. code-block:: console
|
|
26
|
+
|
|
27
|
+
$ docker-services-cli up --search opensearch --db postgresql --cache redis
|
|
28
|
+
|
|
29
|
+
And turn them of once they are not needed anymore:
|
|
30
|
+
|
|
31
|
+
.. code-block:: console
|
|
32
|
+
|
|
33
|
+
$ docker-services-cli down
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__version__ = "0.12.2"
|
|
37
|
+
|
|
38
|
+
__all__ = ("__version__",)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2020 CERN.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""CLI module."""
|
|
5
|
+
|
|
6
|
+
from functools import update_wrapper
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from .config import SERVICE_TYPES
|
|
12
|
+
from .env import (
|
|
13
|
+
normalize_service_name,
|
|
14
|
+
override_default_env,
|
|
15
|
+
print_setup_env_config,
|
|
16
|
+
set_env,
|
|
17
|
+
)
|
|
18
|
+
from .services import services_down, services_up
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_module_path():
|
|
22
|
+
"""Gets the path in which the module is installed."""
|
|
23
|
+
parent_path = Path(__file__).parent
|
|
24
|
+
return str(parent_path)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def env_output(env_set_command):
|
|
28
|
+
"""Decorate command to print exportable environment settings."""
|
|
29
|
+
if env_set_command not in ["export", "unset"]:
|
|
30
|
+
click.secho("Wrong environment set command.", fg="red")
|
|
31
|
+
exit(1)
|
|
32
|
+
|
|
33
|
+
def print_env_output(func):
|
|
34
|
+
@click.option(
|
|
35
|
+
"--env",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
default=False,
|
|
38
|
+
help="Print export statements to set environment.",
|
|
39
|
+
)
|
|
40
|
+
def _print_env_output(*args, **kwargs):
|
|
41
|
+
env = kwargs.pop("env", False)
|
|
42
|
+
services = kwargs.get("services") or SERVICE_TYPES
|
|
43
|
+
if env:
|
|
44
|
+
# comment command output until env export
|
|
45
|
+
click.echo(": '")
|
|
46
|
+
click.get_current_context().invoke(func, *args, **kwargs)
|
|
47
|
+
if env:
|
|
48
|
+
# end of multiline comment, start of export statements
|
|
49
|
+
click.echo("'")
|
|
50
|
+
print_setup_env_config(
|
|
51
|
+
services,
|
|
52
|
+
click.get_current_context().info_name,
|
|
53
|
+
env_set_command=env_set_command,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return update_wrapper(_print_env_output, func)
|
|
57
|
+
|
|
58
|
+
return print_env_output
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def services_by_type(func):
|
|
62
|
+
"""Decorate command adding all service types as options.
|
|
63
|
+
|
|
64
|
+
:param func: The function that implements the Click command to which the
|
|
65
|
+
service types options will be added.
|
|
66
|
+
|
|
67
|
+
:return: A wrapped function around the passed Click command which exposes
|
|
68
|
+
all ``config.SERVICES_TYPES`` as Click options. The list of services
|
|
69
|
+
by type is injected as ``services`` keyword argument.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def collect_services_by_type(*args, **kwargs):
|
|
73
|
+
services = {}
|
|
74
|
+
for service_type in SERVICE_TYPES:
|
|
75
|
+
service = kwargs.pop(service_type)
|
|
76
|
+
if service:
|
|
77
|
+
services.setdefault(service_type, []).extend(
|
|
78
|
+
service if isinstance(service, list) else [service]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
kwargs["services"] = services
|
|
82
|
+
click.get_current_context().invoke(func, *args, **kwargs)
|
|
83
|
+
|
|
84
|
+
def validate_service_name(ctx, service_type, services_list):
|
|
85
|
+
available_services = SERVICE_TYPES.get(service_type.name, [])
|
|
86
|
+
for service in services_list:
|
|
87
|
+
if not (
|
|
88
|
+
normalize_service_name(service)
|
|
89
|
+
or normalize_service_name(service) in available_services
|
|
90
|
+
):
|
|
91
|
+
raise click.BadParameter(
|
|
92
|
+
"{service} is not a valid service of type {type}. "
|
|
93
|
+
"Try one of: \n{available_services}".format(
|
|
94
|
+
service=service,
|
|
95
|
+
type=service_type.name,
|
|
96
|
+
available_services=available_services,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
return list(services_list)
|
|
100
|
+
|
|
101
|
+
for service_type in SERVICE_TYPES:
|
|
102
|
+
click.option(
|
|
103
|
+
"--{}".format(service_type),
|
|
104
|
+
callback=validate_service_name,
|
|
105
|
+
multiple=True,
|
|
106
|
+
help="Specify which service should run as {0}. "
|
|
107
|
+
"Available {0} services: {1}.".format(
|
|
108
|
+
service_type, ", ".join(SERVICE_TYPES.get(service_type))
|
|
109
|
+
),
|
|
110
|
+
)(func)
|
|
111
|
+
|
|
112
|
+
return update_wrapper(collect_services_by_type, func)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ServicesCtx(object):
|
|
116
|
+
"""Context class for docker services cli."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, filepath, verbose):
|
|
119
|
+
"""Constructor."""
|
|
120
|
+
self.filepath = filepath
|
|
121
|
+
self.verbose = verbose
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@click.group()
|
|
125
|
+
@click.version_option()
|
|
126
|
+
@click.option(
|
|
127
|
+
"--filepath",
|
|
128
|
+
"-f",
|
|
129
|
+
required=False,
|
|
130
|
+
default=f"{_get_module_path()}/docker-services.yml",
|
|
131
|
+
type=click.Path(exists=True),
|
|
132
|
+
help="Path to a docker compose file with the desired services definition.",
|
|
133
|
+
)
|
|
134
|
+
@click.option(
|
|
135
|
+
"--verbose",
|
|
136
|
+
is_flag=True,
|
|
137
|
+
default=False,
|
|
138
|
+
help="Verbose output.",
|
|
139
|
+
)
|
|
140
|
+
@click.pass_context
|
|
141
|
+
def cli(ctx, filepath, verbose):
|
|
142
|
+
"""Initialize CLI context."""
|
|
143
|
+
set_env()
|
|
144
|
+
ctx.obj = ServicesCtx(filepath=filepath, verbose=verbose)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@cli.command()
|
|
148
|
+
@click.option(
|
|
149
|
+
"--no-wait",
|
|
150
|
+
is_flag=True,
|
|
151
|
+
help="Wait for services to be up (use healthchecks).",
|
|
152
|
+
)
|
|
153
|
+
@click.option(
|
|
154
|
+
"--retries",
|
|
155
|
+
default=6,
|
|
156
|
+
type=int,
|
|
157
|
+
help="Number of times to retry a service's healthcheck.",
|
|
158
|
+
)
|
|
159
|
+
@services_by_type
|
|
160
|
+
@env_output(env_set_command="export")
|
|
161
|
+
@click.pass_obj
|
|
162
|
+
def up(services_ctx, services, no_wait, retries):
|
|
163
|
+
r"""Boots up the required services.
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
$ docker-services-cli up --db postgresql11
|
|
167
|
+
|
|
168
|
+
Note: All services will be boot up if no service is specified.
|
|
169
|
+
"""
|
|
170
|
+
_services = [s for services_list in services.values() for s in services_list]
|
|
171
|
+
|
|
172
|
+
# NOTE: docker compose boots up all if none is provided
|
|
173
|
+
if len(_services) == 1 and _services[0].lower() == "all":
|
|
174
|
+
_services = []
|
|
175
|
+
|
|
176
|
+
override_default_env(services_to_override=_services)
|
|
177
|
+
click.secho("Environment setup", fg="green")
|
|
178
|
+
|
|
179
|
+
normalized_services = [normalize_service_name(s) for s in _services]
|
|
180
|
+
services_up(
|
|
181
|
+
services=normalized_services,
|
|
182
|
+
filepath=services_ctx.filepath,
|
|
183
|
+
wait=(not no_wait),
|
|
184
|
+
retries=retries,
|
|
185
|
+
verbose=services_ctx.verbose,
|
|
186
|
+
)
|
|
187
|
+
click.secho("Services up!", fg="green")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@cli.command()
|
|
191
|
+
@env_output(env_set_command="unset")
|
|
192
|
+
@click.pass_obj
|
|
193
|
+
def down(services_ctx):
|
|
194
|
+
"""Shuts down the required services."""
|
|
195
|
+
services_down(filepath=services_ctx.filepath)
|
|
196
|
+
click.secho("Services down!", fg="green")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2020-2025 CERN.
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 Graz University of Technology.
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 CESNET z.s.p.o.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
"""Configuration module.
|
|
7
|
+
|
|
8
|
+
Configuration values (e.g. service configuration) need to be set through
|
|
9
|
+
environment variables. However, sane defaults are provided below.
|
|
10
|
+
|
|
11
|
+
The list of services to be configured is taken from ``SERVICES``. Each one
|
|
12
|
+
should contain a ``<SERVICE_NAME>_VERSION`` variable.
|
|
13
|
+
|
|
14
|
+
Service's version are treated slightly different:
|
|
15
|
+
|
|
16
|
+
- If the variable is not found in the environment, it will use the set default.
|
|
17
|
+
- If the variable is set with a version number (e.g. 10, 10.7) it will use
|
|
18
|
+
said value.
|
|
19
|
+
- If the variable is set with a string point to one of the configured
|
|
20
|
+
``latests`` it will load the value of said ``latest`` and use it.
|
|
21
|
+
|
|
22
|
+
This means that the environment set/load logic will first set the default
|
|
23
|
+
versions before loading a given service's version.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
DOCKER_SERVICES_FILEPATH = "docker-services.yml"
|
|
27
|
+
"""Docker services file default path."""
|
|
28
|
+
|
|
29
|
+
# Elasticsearch
|
|
30
|
+
ELASTICSEARCH = {
|
|
31
|
+
"ELASTICSEARCH_VERSION": "ELASTICSEARCH_7_LATEST",
|
|
32
|
+
"DEFAULT_VERSIONS": {
|
|
33
|
+
"ELASTICSEARCH_7_LATEST": "7.10.2", # the last of the OSS versions (https://github.com/elastic/elasticsearch/issues/58303)
|
|
34
|
+
},
|
|
35
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
36
|
+
"search": {
|
|
37
|
+
"SEARCH_HOSTS": "\"[{'host': 'localhost', 'port': 9200}]\"",
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
"""Elasticsearch service configuration."""
|
|
42
|
+
|
|
43
|
+
# Opensearch
|
|
44
|
+
OPENSEARCH = {
|
|
45
|
+
"OPENSEARCH_VERSION": "OPENSEARCH_2_LATEST",
|
|
46
|
+
"DEFAULT_VERSIONS": {
|
|
47
|
+
"OPENSEARCH_1_LATEST": "1.3.18",
|
|
48
|
+
"OPENSEARCH_2_LATEST": "2.16.0",
|
|
49
|
+
},
|
|
50
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
51
|
+
"search": {
|
|
52
|
+
"SEARCH_HOSTS": "\"[{'host': 'localhost', 'port': 9200}]\"",
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
"""Opensearch service configuration."""
|
|
57
|
+
|
|
58
|
+
# PostgreSQL
|
|
59
|
+
POSTGRESQL = {
|
|
60
|
+
"POSTGRESQL_VERSION": "POSTGRESQL_16_LATEST",
|
|
61
|
+
"DEFAULT_VERSIONS": {
|
|
62
|
+
"POSTGRESQL_14_LATEST": "14.9",
|
|
63
|
+
"POSTGRESQL_15_LATEST": "15.4",
|
|
64
|
+
"POSTGRESQL_16_LATEST": "16.2",
|
|
65
|
+
},
|
|
66
|
+
"CONTAINER_CONFIG_ENVIRONMENT_VARIABLES": {
|
|
67
|
+
"POSTGRESQL_USER": "invenio",
|
|
68
|
+
"POSTGRESQL_PASSWORD": "invenio",
|
|
69
|
+
"POSTGRESQL_DB": "invenio",
|
|
70
|
+
},
|
|
71
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
72
|
+
"db": {
|
|
73
|
+
"SQLALCHEMY_DATABASE_URI": "postgresql+psycopg2://invenio:invenio@localhost:5432/invenio"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
"""Postgresql service configuration."""
|
|
78
|
+
|
|
79
|
+
# MySQL
|
|
80
|
+
MYSQL = {
|
|
81
|
+
"MYSQL_VERSION": "MYSQL_8_LATEST",
|
|
82
|
+
"DEFAULT_VERSIONS": {"MYSQL_8_LATEST": "8.3"},
|
|
83
|
+
"CONTAINER_CONFIG_ENVIRONMENT_VARIABLES": {
|
|
84
|
+
"MYSQL_USER": "invenio",
|
|
85
|
+
"MYSQL_PASSWORD": "invenio",
|
|
86
|
+
"MYSQL_DB": "invenio",
|
|
87
|
+
"MYSQL_ROOT_PASSWORD": "invenio",
|
|
88
|
+
},
|
|
89
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
90
|
+
"db": {
|
|
91
|
+
"SQLALCHEMY_DATABASE_URI": "mysql+pymysql://invenio:invenio@localhost:3306/invenio"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
"""MySQL service configuration."""
|
|
96
|
+
|
|
97
|
+
REDIS = {
|
|
98
|
+
"REDIS_VERSION": "REDIS_7_LATEST",
|
|
99
|
+
"DEFAULT_VERSIONS": {
|
|
100
|
+
"REDIS_6_LATEST": "6",
|
|
101
|
+
"REDIS_7_LATEST": "7",
|
|
102
|
+
},
|
|
103
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
104
|
+
"mq": {"BROKER_URL": "redis://localhost:6379/0"},
|
|
105
|
+
"cache": {"CACHE_TYPE": "redis"},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
"""Redis service configuration."""
|
|
109
|
+
|
|
110
|
+
RABBITMQ = {
|
|
111
|
+
"RABBITMQ_VERSION": "RABBITMQ_3_LATEST",
|
|
112
|
+
"DEFAULT_VERSIONS": {"RABBITMQ_3_LATEST": "3"},
|
|
113
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
114
|
+
"mq": {"BROKER_URL": "amqp://localhost:5672//"}
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
"""RabbitMQ service configuration."""
|
|
118
|
+
|
|
119
|
+
MINIO = {
|
|
120
|
+
"MINIO_VERSION": "MINIO_2025_LATEST",
|
|
121
|
+
# note: minio does not do semantic versioning, so we use the latest version
|
|
122
|
+
# the release at the time of writing this is RELEASE.2025-02-28T09-55-16Z
|
|
123
|
+
"DEFAULT_VERSIONS": {"MINIO_2025_LATEST": "latest"},
|
|
124
|
+
"CONTAINER_CONFIG_ENVIRONMENT_VARIABLES": {
|
|
125
|
+
"S3_ACCESS_KEY_ID": "invenio",
|
|
126
|
+
# minio needs at least 8 characters for the secret
|
|
127
|
+
"S3_SECRET_ACCESS_KEY": "invenio8",
|
|
128
|
+
},
|
|
129
|
+
"CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES": {
|
|
130
|
+
"s3": {
|
|
131
|
+
"S3_ENDPOINT_URL": "http://localhost:9000",
|
|
132
|
+
"S3_ACCESS_KEY_ID": "invenio",
|
|
133
|
+
# minio needs at least 8 characters for the secret
|
|
134
|
+
"S3_SECRET_ACCESS_KEY": "invenio8",
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
"""MINIO service configuration."""
|
|
139
|
+
|
|
140
|
+
SERVICES = {
|
|
141
|
+
"elasticsearch": ELASTICSEARCH,
|
|
142
|
+
"opensearch": OPENSEARCH,
|
|
143
|
+
"postgresql": POSTGRESQL,
|
|
144
|
+
"mysql": MYSQL,
|
|
145
|
+
"redis": REDIS,
|
|
146
|
+
"rabbitmq": RABBITMQ,
|
|
147
|
+
"minio": MINIO,
|
|
148
|
+
}
|
|
149
|
+
"""List of services to configure."""
|
|
150
|
+
|
|
151
|
+
SERVICES_ALL_DEFAULT_VERSIONS = {
|
|
152
|
+
**ELASTICSEARCH.get("DEFAULT_VERSIONS", {}),
|
|
153
|
+
**OPENSEARCH.get("DEFAULT_VERSIONS", {}),
|
|
154
|
+
**POSTGRESQL.get("DEFAULT_VERSIONS", {}),
|
|
155
|
+
**REDIS.get("DEFAULT_VERSIONS", {}),
|
|
156
|
+
**MYSQL.get("DEFAULT_VERSIONS", {}),
|
|
157
|
+
**RABBITMQ.get("DEFAULT_VERSIONS", {}),
|
|
158
|
+
**MINIO.get("DEFAULT_VERSIONS", {}),
|
|
159
|
+
}
|
|
160
|
+
"""Services default latest versions."""
|
|
161
|
+
|
|
162
|
+
SERVICE_TYPES = {
|
|
163
|
+
"search": ["opensearch", "elasticsearch"],
|
|
164
|
+
"db": ["mysql", "postgresql"],
|
|
165
|
+
"cache": [
|
|
166
|
+
"redis",
|
|
167
|
+
],
|
|
168
|
+
"mq": ["rabbitmq", "redis"],
|
|
169
|
+
"s3": ["minio"],
|
|
170
|
+
}
|
|
171
|
+
"""Types of offered services."""
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2020-2025 CERN.
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 KTH Royal Institute of Technology.
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 CESNET z.s.p.o.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
redis:
|
|
8
|
+
image: redis:${REDIS_VERSION}
|
|
9
|
+
restart: "unless-stopped"
|
|
10
|
+
read_only: true
|
|
11
|
+
ports:
|
|
12
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:6379:6379"
|
|
13
|
+
mysql:
|
|
14
|
+
image: mysql:${MYSQL_VERSION}
|
|
15
|
+
restart: "unless-stopped"
|
|
16
|
+
environment:
|
|
17
|
+
- "MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}"
|
|
18
|
+
- "MYSQL_DATABASE=${MYSQL_DB}"
|
|
19
|
+
- "MYSQL_USER=${MYSQL_USER}"
|
|
20
|
+
- "MYSQL_PASSWORD=${MYSQL_PASSWORD}"
|
|
21
|
+
ports:
|
|
22
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:3306:3306"
|
|
23
|
+
postgresql:
|
|
24
|
+
image: postgres:${POSTGRESQL_VERSION}
|
|
25
|
+
restart: "unless-stopped"
|
|
26
|
+
environment:
|
|
27
|
+
- "POSTGRES_USER=${POSTGRESQL_USER}"
|
|
28
|
+
- "POSTGRES_PASSWORD=${POSTGRESQL_PASSWORD}"
|
|
29
|
+
- "POSTGRES_DB=${POSTGRESQL_DB}"
|
|
30
|
+
ports:
|
|
31
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:5432:5432"
|
|
32
|
+
elasticsearch:
|
|
33
|
+
image: docker.elastic.co/elasticsearch/elasticsearch-oss:${ELASTICSEARCH_VERSION}
|
|
34
|
+
restart: "unless-stopped"
|
|
35
|
+
environment:
|
|
36
|
+
- bootstrap.memory_lock=true
|
|
37
|
+
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
38
|
+
- discovery.type=single-node
|
|
39
|
+
ulimits:
|
|
40
|
+
memlock:
|
|
41
|
+
soft: -1
|
|
42
|
+
hard: -1
|
|
43
|
+
mem_limit: 2g
|
|
44
|
+
ports:
|
|
45
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9200:9200"
|
|
46
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9300:9300"
|
|
47
|
+
opensearch:
|
|
48
|
+
image: opensearchproject/opensearch:${OPENSEARCH_VERSION}
|
|
49
|
+
restart: "unless-stopped"
|
|
50
|
+
environment:
|
|
51
|
+
- bootstrap.memory_lock=true
|
|
52
|
+
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
|
|
53
|
+
- "OPENSEARCH_INITIAL_ADMIN_PASSWORD=guekxe3mvqieke1!%&ieIADE"
|
|
54
|
+
- discovery.type=single-node
|
|
55
|
+
- plugins.security.disabled=true
|
|
56
|
+
ulimits:
|
|
57
|
+
memlock:
|
|
58
|
+
soft: -1
|
|
59
|
+
hard: -1
|
|
60
|
+
nofile:
|
|
61
|
+
soft: 65536
|
|
62
|
+
hard: 65536
|
|
63
|
+
mem_limit: 2g
|
|
64
|
+
ports:
|
|
65
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9200:9200"
|
|
66
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9300:9300"
|
|
67
|
+
rabbitmq:
|
|
68
|
+
image: rabbitmq:${RABBITMQ_VERSION}-management
|
|
69
|
+
restart: "unless-stopped"
|
|
70
|
+
ports:
|
|
71
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:15672:15672"
|
|
72
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:5672:5672"
|
|
73
|
+
minio:
|
|
74
|
+
image: minio/minio:${MINIO_VERSION}
|
|
75
|
+
restart: "unless-stopped"
|
|
76
|
+
environment:
|
|
77
|
+
MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID}
|
|
78
|
+
MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY}
|
|
79
|
+
entrypoint: >
|
|
80
|
+
/bin/sh -c "
|
|
81
|
+
mkdir -p /data/default;
|
|
82
|
+
/usr/bin/docker-entrypoint.sh server /data --console-address :9001
|
|
83
|
+
"
|
|
84
|
+
ports:
|
|
85
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9000:9000"
|
|
86
|
+
- "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9001:9001"
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2020 CERN.
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 Graz University of Technology.
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 CESNET z.s.p.o.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
"""Environment module."""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from distutils.version import StrictVersion
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from .config import SERVICE_TYPES, SERVICES, SERVICES_ALL_DEFAULT_VERSIONS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalize_service_name(service_with_version):
|
|
19
|
+
"""Return the name of the passed service without version number."""
|
|
20
|
+
service_name = None
|
|
21
|
+
if service_with_version in SERVICES:
|
|
22
|
+
service_name = service_with_version
|
|
23
|
+
else:
|
|
24
|
+
for name in SERVICES:
|
|
25
|
+
if name in service_with_version:
|
|
26
|
+
service_name = name
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
return service_name
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _set_default_env(services_version, default_version):
|
|
33
|
+
"""Set environmental variable value if it does not exist."""
|
|
34
|
+
os.environ[services_version] = os.environ.get(services_version, default_version)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_version(version):
|
|
38
|
+
"""Checks if a string is a version of the format `x.y.z`.
|
|
39
|
+
|
|
40
|
+
NOTE: It is not mandatory to be up to patch level. The following would be
|
|
41
|
+
accepted:
|
|
42
|
+
- 10.1
|
|
43
|
+
- 9
|
|
44
|
+
- 15.0.1a2
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
# StrictVersion fails on plain numbers (e.g. "10")
|
|
48
|
+
if version.isnumeric():
|
|
49
|
+
return True
|
|
50
|
+
StrictVersion(version)
|
|
51
|
+
return True
|
|
52
|
+
except Exception:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _load_or_set_env(services_version, default_version):
|
|
57
|
+
"""Set a specific service version from the environment.
|
|
58
|
+
|
|
59
|
+
It parses the value to distinguish between a version and a defined latest.
|
|
60
|
+
NOTE: It requires that all variables for latest versions have been set up.
|
|
61
|
+
"""
|
|
62
|
+
version_from_env = os.environ.get(services_version, default_version)
|
|
63
|
+
# e.g. the ES_7_LATEST string from env, need a second get.
|
|
64
|
+
major_version_from_env = os.environ.get(version_from_env)
|
|
65
|
+
|
|
66
|
+
if not version_from_env:
|
|
67
|
+
os.environ[services_version] = default_version
|
|
68
|
+
|
|
69
|
+
elif (
|
|
70
|
+
_is_version(version_from_env)
|
|
71
|
+
# for example for minio, where we do not have a semantic version
|
|
72
|
+
or version_from_env == "latest"
|
|
73
|
+
):
|
|
74
|
+
os.environ[services_version] = version_from_env
|
|
75
|
+
|
|
76
|
+
elif major_version_from_env and (
|
|
77
|
+
_is_version(major_version_from_env)
|
|
78
|
+
# for example for minio, where we do not have a semantic version
|
|
79
|
+
or major_version_from_env == "latest"
|
|
80
|
+
):
|
|
81
|
+
os.environ[services_version] = major_version_from_env
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
click.secho(
|
|
85
|
+
f"Environment variable for version {version_from_env} not set \
|
|
86
|
+
or set to a non-compliant format (dot separated numbers).",
|
|
87
|
+
fg="red",
|
|
88
|
+
)
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def override_default_env(services_to_override=None):
|
|
93
|
+
"""Override default environment according to list of services with version.
|
|
94
|
+
|
|
95
|
+
:param services_to_override: List of service name strings including
|
|
96
|
+
service version without any separator e.g. ``postgresql11``.
|
|
97
|
+
"""
|
|
98
|
+
services_to_override = set(services_to_override or []).difference(SERVICES.keys())
|
|
99
|
+
if services_to_override:
|
|
100
|
+
num_services_to_override = len(services_to_override)
|
|
101
|
+
for service_name in SERVICES:
|
|
102
|
+
for service_override in services_to_override:
|
|
103
|
+
if service_name in service_override:
|
|
104
|
+
service_override_version = service_override.replace(
|
|
105
|
+
service_name, ""
|
|
106
|
+
)
|
|
107
|
+
env_var_with_version = (
|
|
108
|
+
f"{service_name.upper()}_{service_override_version}_LATEST"
|
|
109
|
+
)
|
|
110
|
+
if SERVICES_ALL_DEFAULT_VERSIONS.get(env_var_with_version):
|
|
111
|
+
os.environ[f"{service_name.upper()}_VERSION"] = (
|
|
112
|
+
SERVICES_ALL_DEFAULT_VERSIONS.get(env_var_with_version)
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
available_major_versions = [
|
|
116
|
+
v.split(".")[0]
|
|
117
|
+
for v in SERVICES[service_name]["DEFAULT_VERSIONS"].values()
|
|
118
|
+
]
|
|
119
|
+
click.secho(
|
|
120
|
+
f"No major version {service_override_version} "
|
|
121
|
+
f"for {service_name}. "
|
|
122
|
+
"Please use one of the available "
|
|
123
|
+
f"ones: {available_major_versions}",
|
|
124
|
+
fg="red",
|
|
125
|
+
)
|
|
126
|
+
exit(1)
|
|
127
|
+
num_services_to_override -= 1
|
|
128
|
+
if not num_services_to_override:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def set_env():
|
|
133
|
+
"""Export the environment variables for services and versions."""
|
|
134
|
+
for key, value in SERVICES_ALL_DEFAULT_VERSIONS.items():
|
|
135
|
+
_set_default_env(key, value)
|
|
136
|
+
|
|
137
|
+
for service in SERVICES.values():
|
|
138
|
+
for key, value in service.items():
|
|
139
|
+
if key.endswith("_VERSION"):
|
|
140
|
+
_load_or_set_env(key, value)
|
|
141
|
+
elif key == "CONTAINER_CONFIG_ENVIRONMENT_VARIABLES":
|
|
142
|
+
for envvar_name, envvar_value in value.items():
|
|
143
|
+
_set_default_env(envvar_name, envvar_value)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def print_setup_env_config(services, called_from, env_set_command="export"):
|
|
147
|
+
"""Prints setup environment instructions."""
|
|
148
|
+
should_print_instructions = False
|
|
149
|
+
for service_type, services_list in services.items():
|
|
150
|
+
if called_from == "up" and len(services_list) > 1:
|
|
151
|
+
logging.warning(
|
|
152
|
+
"Multiple %s services %s are being configured. "
|
|
153
|
+
"Note that only %s will be accessible.",
|
|
154
|
+
service_type,
|
|
155
|
+
services_list,
|
|
156
|
+
services_list[-1],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
for key, value in get_service_env_vars(service_type, services_list):
|
|
160
|
+
command = f"{env_set_command} {key}"
|
|
161
|
+
if env_set_command == "export":
|
|
162
|
+
command += f"={value}"
|
|
163
|
+
click.echo(command)
|
|
164
|
+
should_print_instructions = True
|
|
165
|
+
|
|
166
|
+
if should_print_instructions:
|
|
167
|
+
click.secho("# Configure your environment running:", fg="yellow")
|
|
168
|
+
instructions = f'# eval "$(docker-services-cli {called_from}'
|
|
169
|
+
if called_from == "up" and services != SERVICE_TYPES:
|
|
170
|
+
instructions += " " + " ".join(
|
|
171
|
+
[
|
|
172
|
+
"--{0} {1}".format(service_type, service)
|
|
173
|
+
for service_type, services_list in services.items()
|
|
174
|
+
for service in services_list
|
|
175
|
+
]
|
|
176
|
+
)
|
|
177
|
+
instructions += ' --env)"'
|
|
178
|
+
click.secho(instructions, fg="yellow")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_service_env_vars(service_type, services_list):
|
|
182
|
+
"""Get all or a subset of service environment variables."""
|
|
183
|
+
envvars = []
|
|
184
|
+
for service in services_list:
|
|
185
|
+
service_envvars_by_type = (
|
|
186
|
+
SERVICES.get(normalize_service_name(service))
|
|
187
|
+
.get("CONTAINER_CONNECTION_ENVIRONMENT_VARIABLES", {})
|
|
188
|
+
.get(service_type, {})
|
|
189
|
+
.items()
|
|
190
|
+
)
|
|
191
|
+
for key, value in service_envvars_by_type:
|
|
192
|
+
envvars.append((key, value))
|
|
193
|
+
|
|
194
|
+
return envvars
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2020 CERN.
|
|
2
|
+
# SPDX-FileCopyrightText: 2024 Graz University of Technology.
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 CESNET z.s.p.o.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
"""Services module."""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from os import path
|
|
10
|
+
from subprocess import PIPE, Popen, check_call
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from .config import DOCKER_SERVICES_FILEPATH, MYSQL, SERVICE_TYPES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _run_healthcheck_command(command, verbose=False):
|
|
18
|
+
"""Runs a given command, returns True if it succeeds, False otherwise."""
|
|
19
|
+
p = Popen(command, stdout=PIPE, stderr=PIPE)
|
|
20
|
+
output, error = p.communicate()
|
|
21
|
+
output = output.decode("utf-8")
|
|
22
|
+
error = error.decode("utf-8")
|
|
23
|
+
if p.returncode == 0:
|
|
24
|
+
if verbose:
|
|
25
|
+
click.secho(output, fg="green")
|
|
26
|
+
return True
|
|
27
|
+
if p.returncode != 0:
|
|
28
|
+
if verbose:
|
|
29
|
+
click.secho(
|
|
30
|
+
f"Healthcheck failed.\nOutput: {output}\nError:{error}", fg="red"
|
|
31
|
+
)
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def search_healthcheck(*args, **kwargs):
|
|
36
|
+
"""{Elastic,Open}Search healthcheck."""
|
|
37
|
+
verbose = kwargs["verbose"]
|
|
38
|
+
|
|
39
|
+
return _run_healthcheck_command(
|
|
40
|
+
["curl", "-f", "localhost:9200/_cluster/health?wait_for_status=green"], verbose
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def postgresql_healthcheck(*args, **kwargs):
|
|
45
|
+
"""Postgresql healthcheck."""
|
|
46
|
+
filepath = kwargs["filepath"]
|
|
47
|
+
verbose = kwargs["verbose"]
|
|
48
|
+
|
|
49
|
+
return _run_healthcheck_command(
|
|
50
|
+
[
|
|
51
|
+
"docker",
|
|
52
|
+
"compose",
|
|
53
|
+
"--file",
|
|
54
|
+
filepath,
|
|
55
|
+
"exec",
|
|
56
|
+
"-T",
|
|
57
|
+
"postgresql",
|
|
58
|
+
"bash",
|
|
59
|
+
"-c",
|
|
60
|
+
"pg_isready",
|
|
61
|
+
],
|
|
62
|
+
verbose,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def mysql_healthcheck(*args, **kwargs):
|
|
67
|
+
"""Mysql healthcheck."""
|
|
68
|
+
filepath = kwargs["filepath"]
|
|
69
|
+
verbose = kwargs["verbose"]
|
|
70
|
+
password = MYSQL["CONTAINER_CONFIG_ENVIRONMENT_VARIABLES"]["MYSQL_ROOT_PASSWORD"]
|
|
71
|
+
|
|
72
|
+
return _run_healthcheck_command(
|
|
73
|
+
[
|
|
74
|
+
"docker",
|
|
75
|
+
"compose",
|
|
76
|
+
"--file",
|
|
77
|
+
filepath,
|
|
78
|
+
"exec",
|
|
79
|
+
"-T",
|
|
80
|
+
"mysql",
|
|
81
|
+
"bash",
|
|
82
|
+
"-c",
|
|
83
|
+
f'mysql -p{password} -e "select Version();"',
|
|
84
|
+
],
|
|
85
|
+
verbose,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def rabbitmq_healthcheck(*args, **kwargs):
|
|
90
|
+
"""Rabbitmq healthcheck."""
|
|
91
|
+
filepath = kwargs["filepath"]
|
|
92
|
+
verbose = kwargs["verbose"]
|
|
93
|
+
|
|
94
|
+
return _run_healthcheck_command(
|
|
95
|
+
[
|
|
96
|
+
"docker",
|
|
97
|
+
"compose",
|
|
98
|
+
"--file",
|
|
99
|
+
filepath,
|
|
100
|
+
"exec",
|
|
101
|
+
"-T",
|
|
102
|
+
"rabbitmq",
|
|
103
|
+
"bash",
|
|
104
|
+
"-c",
|
|
105
|
+
"rabbitmq-diagnostics check_running",
|
|
106
|
+
],
|
|
107
|
+
verbose,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def redis_healthcheck(*args, **kwargs):
|
|
112
|
+
"""Redis healthcheck."""
|
|
113
|
+
filepath = kwargs["filepath"]
|
|
114
|
+
verbose = kwargs["verbose"]
|
|
115
|
+
|
|
116
|
+
return _run_healthcheck_command(
|
|
117
|
+
[
|
|
118
|
+
"docker",
|
|
119
|
+
"compose",
|
|
120
|
+
"--file",
|
|
121
|
+
filepath,
|
|
122
|
+
"exec",
|
|
123
|
+
"-T",
|
|
124
|
+
"redis",
|
|
125
|
+
"bash",
|
|
126
|
+
"-c",
|
|
127
|
+
"redis-cli ping",
|
|
128
|
+
"|",
|
|
129
|
+
"grep 'PONG'",
|
|
130
|
+
"&>/dev/null;",
|
|
131
|
+
],
|
|
132
|
+
verbose,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def minio_healthcheck(*args, **kwargs):
|
|
137
|
+
"""Minio healthcheck."""
|
|
138
|
+
verbose = kwargs["verbose"]
|
|
139
|
+
|
|
140
|
+
return _run_healthcheck_command(
|
|
141
|
+
["curl", "-f", "http://localhost:9000/minio/health/live"], verbose
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
HEALTHCHECKS = {
|
|
146
|
+
"elasticsearch": search_healthcheck,
|
|
147
|
+
"opensearch": search_healthcheck,
|
|
148
|
+
"postgresql": postgresql_healthcheck,
|
|
149
|
+
"mysql": mysql_healthcheck,
|
|
150
|
+
"rabbitmq": rabbitmq_healthcheck,
|
|
151
|
+
"redis": redis_healthcheck,
|
|
152
|
+
"minio": minio_healthcheck,
|
|
153
|
+
}
|
|
154
|
+
"""Health check functions module path, as string."""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def wait_for_services(
|
|
158
|
+
services, filepath=DOCKER_SERVICES_FILEPATH, max_retries=6, verbose=False
|
|
159
|
+
):
|
|
160
|
+
"""Wait for services to be up.
|
|
161
|
+
|
|
162
|
+
It performs configured healthchecks in a serial fashion, following the
|
|
163
|
+
order given in the ``up`` command. If the services is an empty list, to be
|
|
164
|
+
compliant with `docker compose` it will perform the healthchecks of all the
|
|
165
|
+
services.
|
|
166
|
+
"""
|
|
167
|
+
if len(services) == 0:
|
|
168
|
+
services = HEALTHCHECKS.keys()
|
|
169
|
+
|
|
170
|
+
for service in services:
|
|
171
|
+
exp_backoff_time = 2
|
|
172
|
+
try_ = 1
|
|
173
|
+
# Using plain __import__ to avoid depending on invenio-base
|
|
174
|
+
check = HEALTHCHECKS[service]
|
|
175
|
+
ready = check(filepath=filepath, verbose=verbose)
|
|
176
|
+
while not ready and try_ < max_retries:
|
|
177
|
+
click.secho(
|
|
178
|
+
f"{service} not ready at {try_} retries, waiting {exp_backoff_time}s",
|
|
179
|
+
fg="yellow",
|
|
180
|
+
)
|
|
181
|
+
try_ += 1
|
|
182
|
+
time.sleep(exp_backoff_time)
|
|
183
|
+
exp_backoff_time *= 2
|
|
184
|
+
ready = check(filepath=filepath, verbose=verbose)
|
|
185
|
+
|
|
186
|
+
if not ready:
|
|
187
|
+
click.secho(f"Unable to boot up {service}", fg="red")
|
|
188
|
+
exit(1)
|
|
189
|
+
else:
|
|
190
|
+
click.secho(f"{service} up and running!", fg="green")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def services_up(
|
|
194
|
+
services, filepath=DOCKER_SERVICES_FILEPATH, wait=True, retries=6, verbose=False
|
|
195
|
+
):
|
|
196
|
+
"""Start the given services up.
|
|
197
|
+
|
|
198
|
+
docker compose is smart about not rebuilding an image if
|
|
199
|
+
there is no need to, so --build is not a slow default. In addition
|
|
200
|
+
``--detach`` is not supported in 1.17.0 or previous.
|
|
201
|
+
"""
|
|
202
|
+
services = services or [
|
|
203
|
+
service for _, services in SERVICE_TYPES.items() for service in services
|
|
204
|
+
]
|
|
205
|
+
if not path.exists(filepath):
|
|
206
|
+
click.secho(
|
|
207
|
+
f"Filepath {filepath} for docker-services.yml file does not exist.",
|
|
208
|
+
fg="red",
|
|
209
|
+
)
|
|
210
|
+
exit(1)
|
|
211
|
+
|
|
212
|
+
command = ["docker", "compose", "--file", filepath, "up", "-d"]
|
|
213
|
+
command.extend(services)
|
|
214
|
+
|
|
215
|
+
check_call(command)
|
|
216
|
+
if wait:
|
|
217
|
+
wait_for_services(services, filepath, max_retries=retries, verbose=verbose)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def services_down(filepath=DOCKER_SERVICES_FILEPATH):
|
|
221
|
+
"""Stops the given services.
|
|
222
|
+
|
|
223
|
+
It does not requries the services. It stops containers and removes
|
|
224
|
+
containers, networks, volumes, and images created by ``up``.
|
|
225
|
+
"""
|
|
226
|
+
command = ["docker", "compose", "--file", filepath, "down", "--volumes"]
|
|
227
|
+
|
|
228
|
+
check_call(command)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Docker-Services-CLI
|
|
3
|
+
Version: 0.12.2
|
|
4
|
+
Summary: Python module to ease the creation and management of external services.
|
|
5
|
+
Project-URL: Repository, https://github.com/inveniosoftware/docker-services-cli
|
|
6
|
+
Author-email: CERN <info@inveniosoftware.org>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: AUTHORS.rst
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cli,docker,services
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Requires-Dist: check-manifest>=0.42
|
|
14
|
+
Requires-Dist: click>=7.0
|
|
15
|
+
Provides-Extra: tests
|
|
16
|
+
Requires-Dist: pytest-black>=0.6.0; extra == 'tests'
|
|
17
|
+
Requires-Dist: pytest-cov>=3.0.0; extra == 'tests'
|
|
18
|
+
Requires-Dist: pytest-isort>=3.0.0; extra == 'tests'
|
|
19
|
+
Requires-Dist: pytest-pycodestyle>=2.2.0; extra == 'tests'
|
|
20
|
+
Requires-Dist: pytest-pydocstyle>=2.2.3; extra == 'tests'
|
|
21
|
+
Requires-Dist: pytest<9,>=6; extra == 'tests'
|
|
22
|
+
Requires-Dist: sphinx>=4.5; extra == 'tests'
|
|
23
|
+
Description-Content-Type: text/x-rst
|
|
24
|
+
|
|
25
|
+
..
|
|
26
|
+
SPDX-FileCopyrightText: 2020 CERN.
|
|
27
|
+
SPDX-License-Identifier: MIT
|
|
28
|
+
|
|
29
|
+
===================
|
|
30
|
+
Docker-Services-CLI
|
|
31
|
+
===================
|
|
32
|
+
|
|
33
|
+
.. image:: https://github.com/inveniosoftware/docker-services-cli/workflows/CI/badge.svg
|
|
34
|
+
:target: https://github.com/inveniosoftware/docker-services-cli/actions?query=workflow%3ACI
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Python module to start external services needed to tests Invenio modules and applications.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
docker_services_cli/__init__.py,sha256=BaX_kDwfAhN4OT57WySTapi4mYceFrC6MJOGEzkcBP4,1010
|
|
2
|
+
docker_services_cli/cli.py,sha256=CXC59Vqy2IRVb0sbOVbIfcHx-FW9tfnWzOIAbiNxxT4,5957
|
|
3
|
+
docker_services_cli/config.py,sha256=YK4y8gAsNKwylwlxbAl7KRjfs_B1tMq-s0A9fBsSsVc,5327
|
|
4
|
+
docker_services_cli/docker-services.yml,sha256=w2bmFxB73BrTUyBAHUJ-M0AG1BPRhyfY9vo9g9KNTKg,2757
|
|
5
|
+
docker_services_cli/env.py,sha256=7ILguYJ-3eOqO8_vyKjEfEyVRGlr-I1gsU83hyqzKM8,7137
|
|
6
|
+
docker_services_cli/services.py,sha256=TTRqlOASTM-gDgLjfd2nKsChv5BhRrQV4G8HfrjNveg,6170
|
|
7
|
+
docker_services_cli-0.12.2.dist-info/METADATA,sha256=mK5noipC-wSj3GJS6BHLHzhYGceB1qHXNH3mI2wvdf4,1346
|
|
8
|
+
docker_services_cli-0.12.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
9
|
+
docker_services_cli-0.12.2.dist-info/entry_points.txt,sha256=KB-GjGrrAvFWMQ-GRsO_a-INQeO7yPh2_GkPlj80tFQ,68
|
|
10
|
+
docker_services_cli-0.12.2.dist-info/licenses/AUTHORS.rst,sha256=j2PS73v5ajTs3mjQ1zQLxnP7H1-Eq8WcbIF0cAhm86w,130
|
|
11
|
+
docker_services_cli-0.12.2.dist-info/licenses/LICENSE,sha256=ZiCjjVShFiNyAecBnVjHFO3Wzfrp86N8j_pffcCZpE8,1061
|
|
12
|
+
docker_services_cli-0.12.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 CERN
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|