arcsecond 4.2.1__tar.gz → 4.3.1__tar.gz
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.
- {arcsecond-4.2.1 → arcsecond-4.3.1}/PKG-INFO +1 -1
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/backups.py +4 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/composedoc.py +31 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/database.py +10 -6
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/docker/docker-compose.yml +57 -2
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/lifecycle.py +1 -1
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/token.py +43 -2
- {arcsecond-4.2.1 → arcsecond-4.3.1}/pyproject.toml +1 -1
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_composedoc.py +1 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_database.py +17 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_token.py +36 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/uv.lock +1 -1
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.docker/Dockerfile_postgres +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.docker/Dockerfile_redis +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.flake8 +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.gitattributes +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.github/dependabot.yml +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.github/workflows/pythonpublish.yml +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.github/workflows/tests.yml +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/.gitignore +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/LICENSE +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/Makefile +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/README.md +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/__version__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/alpaca/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/alpaca/commands.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/alpaca/dome_probe.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/alpaca/probe.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/alpaca/telescope_probe.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/api/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/api/config.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/api/constants.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/api/endpoint.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/api/main.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/api/resources.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cli.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/auth.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/resources.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/constants.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/context.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/errors.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/logger.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/uploader.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/utils.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploader/walker.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/cloud/uploads.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/docgen.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/errors.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/check.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/docker/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/local.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/stack.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/hosting/utils.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/autostart.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/commands.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/detection.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/proxy.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/registry.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/runtime.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/sources/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/sources/base.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/sources/filewatch.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/sources/mdns.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/sources/network.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/sources/opencv.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/imagesources/store.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/options.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/arcsecond/targets.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/examples/example_upload_files.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/requirements.txt +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/setup.cfg +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/setup.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/sonar-project.properties +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/api/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/api/test_api.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/api/test_api_endpoint.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/api/test_config.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/api/test_config_pointer.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/api/test_targets.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/conftest.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/fixtures/file1.fits +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_alpaca_probe.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_cli.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_cli_api_commands.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_docgen.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_backups_install_dir.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_backups_restore.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_check.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_lifecycle.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_local.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_stack.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_hosting_utils_secret_key.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_autostart.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_commands.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_detection.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_mdns.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_network.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_proxy.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_store.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_imagesources_webcam.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/test_targets_planning.py +0 -0
- {arcsecond-4.2.1 → arcsecond-4.3.1}/tests/utils.py +0 -0
|
@@ -21,6 +21,9 @@ from .utils import _read_env_value as _read_env_value_from
|
|
|
21
21
|
DB_CONTAINER = "arcsecond-db"
|
|
22
22
|
API_CONTAINER = "arcsecond-api"
|
|
23
23
|
WORKER_CONTAINER = "arcsecond-worker"
|
|
24
|
+
# Since docker-compose.yml 7.3; absent on older installs, which the stop loop
|
|
25
|
+
# and the docker-start fallback both tolerate, as for alerts below.
|
|
26
|
+
DATA_WORKER_CONTAINER = "arcsecond-dataworker"
|
|
24
27
|
BEAT_CONTAINER = "arcsecond-beat"
|
|
25
28
|
WEB_CONTAINER = "arcsecond-web"
|
|
26
29
|
# Optional service: absent on most installs. The stop loop skips it when it
|
|
@@ -31,6 +34,7 @@ ALERTS_CONTAINER = "arcsecond-alerts"
|
|
|
31
34
|
SERVICES_TO_STOP = [
|
|
32
35
|
API_CONTAINER,
|
|
33
36
|
WORKER_CONTAINER,
|
|
37
|
+
DATA_WORKER_CONTAINER,
|
|
34
38
|
ALERTS_CONTAINER,
|
|
35
39
|
BEAT_CONTAINER,
|
|
36
40
|
WEB_CONTAINER,
|
|
@@ -251,6 +251,37 @@ ENV_KEYS: Dict[str, tuple] = {
|
|
|
251
251
|
"How long a new member may use the installation before verifying their email, "
|
|
252
252
|
"on an installation with no mail server.",
|
|
253
253
|
),
|
|
254
|
+
"ARCSECOND_DOWNLOADS_PER_CLIENT": (
|
|
255
|
+
OPERATOR,
|
|
256
|
+
"How many downloads one computer may have open at once; past it, the next one is refused with a 429 "
|
|
257
|
+
"until one finishes. Default 6. Then `arcsecond restart web`.",
|
|
258
|
+
),
|
|
259
|
+
"ARCSECOND_DOWNLOADS_TOTAL": (
|
|
260
|
+
OPERATOR,
|
|
261
|
+
"How many downloads the installation serves at once, all computers together. Default 24.",
|
|
262
|
+
),
|
|
263
|
+
"ARCSECOND_DOWNLOAD_RATE": (
|
|
264
|
+
OPERATOR,
|
|
265
|
+
"The speed of each download, in bytes per second, `k` and `m` allowed; `0` for no limit. Default `20m`. "
|
|
266
|
+
"Lower it when the observers work remotely over a link the downloads would otherwise fill.",
|
|
267
|
+
),
|
|
268
|
+
"ARCSECOND_BACKEND_MEMORY": (
|
|
269
|
+
OPERATOR,
|
|
270
|
+
"The most memory the backend container may use, e.g. `6g` (the default). Past it, Docker restarts the "
|
|
271
|
+
"busiest of its processes rather than letting the machine take memory from the database.",
|
|
272
|
+
),
|
|
273
|
+
"ARCSECOND_WORKER_MEMORY": (
|
|
274
|
+
OPERATOR,
|
|
275
|
+
"The same ceiling for the worker, which runs the exposures and procedures. Default `6g`.",
|
|
276
|
+
),
|
|
277
|
+
"ARCSECOND_DATA_WORKER_MEMORY": (
|
|
278
|
+
OPERATOR,
|
|
279
|
+
"The same ceiling for the data worker: zip archives, previews, storage pushes, backups. Default `3g`.",
|
|
280
|
+
),
|
|
281
|
+
"ARCSECOND_DATA_WORKER_CONCURRENCY": (
|
|
282
|
+
OPERATOR,
|
|
283
|
+
"How many jobs the data worker runs at once. Default 2; each can hold a few hundred MB.",
|
|
284
|
+
),
|
|
254
285
|
}
|
|
255
286
|
|
|
256
287
|
|
|
@@ -44,16 +44,20 @@ SERVICES_TO_RECREATE = ["backend", "worker", "beat"]
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def _services_to_recreate():
|
|
47
|
-
"""The optional alerts consumer
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
"""The optional alerts consumer, and the data worker of docker-compose.yml
|
|
48
|
+
7.3 and later, join the recreate list only when the operator's compose
|
|
49
|
+
file actually carries them — naming an unknown service makes
|
|
50
|
+
`docker compose up` fail outright on installs without them."""
|
|
50
51
|
services = list(SERVICES_TO_RECREATE)
|
|
51
52
|
compose_path = Path.cwd() / "docker-compose.yml"
|
|
52
53
|
try:
|
|
53
|
-
|
|
54
|
-
services.append("alerts")
|
|
54
|
+
text = compose_path.read_text(encoding="utf-8")
|
|
55
55
|
except OSError:
|
|
56
|
-
|
|
56
|
+
return services
|
|
57
|
+
if "container_name: arcsecond-dataworker" in text:
|
|
58
|
+
services.append("dataworker")
|
|
59
|
+
if "# >>> arcsecond:alerts" in text:
|
|
60
|
+
services.append("alerts")
|
|
57
61
|
return services
|
|
58
62
|
|
|
59
63
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# === Arcsecond.local config. ===
|
|
2
2
|
# *** DO NOT change any name or port number. ***
|
|
3
|
-
# Version
|
|
3
|
+
# Version 7.3
|
|
4
4
|
|
|
5
5
|
services:
|
|
6
6
|
# Database (PostgresQL)
|
|
@@ -54,6 +54,10 @@ services:
|
|
|
54
54
|
bind:
|
|
55
55
|
create_host_path: true
|
|
56
56
|
stop_grace_period: 60s
|
|
57
|
+
# A ceiling, so no burst of work here can take the memory Postgres needs.
|
|
58
|
+
# Downloads no longer pass through these processes (the web container
|
|
59
|
+
# sends them), but rendering a large frame for review still does.
|
|
60
|
+
mem_limit: ${ARCSECOND_BACKEND_MEMORY:-6g}
|
|
57
61
|
healthcheck:
|
|
58
62
|
test: [ "CMD", "curl", "-f", "http://localhost:8800/healthcheck/" ]
|
|
59
63
|
interval: 10s
|
|
@@ -61,7 +65,8 @@ services:
|
|
|
61
65
|
retries: 12
|
|
62
66
|
start_period: 120s
|
|
63
67
|
|
|
64
|
-
# Arcsecond worker, for offloading background work
|
|
68
|
+
# Arcsecond worker, for offloading background work: the Control Room's
|
|
69
|
+
# exposures and procedures, and everything else someone is waiting on.
|
|
65
70
|
worker:
|
|
66
71
|
image: ghcr.io/arcsecond-io/arcsecond-api:latest
|
|
67
72
|
container_name: arcsecond-worker
|
|
@@ -72,6 +77,9 @@ services:
|
|
|
72
77
|
condition: service_healthy
|
|
73
78
|
env_file:
|
|
74
79
|
- .env
|
|
80
|
+
environment:
|
|
81
|
+
# The default queue only: the data worker below takes the rest.
|
|
82
|
+
ARCSECOND_WORKER_QUEUES: celery
|
|
75
83
|
volumes:
|
|
76
84
|
# Leave /data as is, it's a path inside the container, not in the host machine.
|
|
77
85
|
# SHARED_DATA_PATH must be a path of your host machine, specified in the .env file.
|
|
@@ -81,6 +89,36 @@ services:
|
|
|
81
89
|
bind:
|
|
82
90
|
create_host_path: true
|
|
83
91
|
stop_grace_period: 120s
|
|
92
|
+
mem_limit: ${ARCSECOND_WORKER_MEMORY:-6g}
|
|
93
|
+
|
|
94
|
+
# Arcsecond data worker, for the background work nobody at the telescope is
|
|
95
|
+
# waiting on: zip archives, previews of uploaded files, pushes to your
|
|
96
|
+
# storages, database backups, keograms, catalogue refreshes. On its own
|
|
97
|
+
# queue, so a long zip can never hold up a frame the observer is waiting for.
|
|
98
|
+
dataworker:
|
|
99
|
+
image: ghcr.io/arcsecond-io/arcsecond-api:latest
|
|
100
|
+
container_name: arcsecond-dataworker
|
|
101
|
+
restart: unless-stopped
|
|
102
|
+
entrypoint: /home/app/entrypoint-worker.sh
|
|
103
|
+
depends_on:
|
|
104
|
+
backend:
|
|
105
|
+
condition: service_healthy
|
|
106
|
+
env_file:
|
|
107
|
+
- .env
|
|
108
|
+
environment:
|
|
109
|
+
ARCSECOND_WORKER_QUEUES: data
|
|
110
|
+
# Few at a time: this work is heavy and none of it is urgent.
|
|
111
|
+
ARCSECOND_WORKER_CONCURRENCY: ${ARCSECOND_DATA_WORKER_CONCURRENCY:-2}
|
|
112
|
+
volumes:
|
|
113
|
+
# Leave /data as is, it's a path inside the container, not in the host machine.
|
|
114
|
+
# SHARED_DATA_PATH must be a path of your host machine, specified in the .env file.
|
|
115
|
+
- type: bind
|
|
116
|
+
source: ${SHARED_DATA_PATH}
|
|
117
|
+
target: /data
|
|
118
|
+
bind:
|
|
119
|
+
create_host_path: true
|
|
120
|
+
stop_grace_period: 120s
|
|
121
|
+
mem_limit: ${ARCSECOND_DATA_WORKER_MEMORY:-3g}
|
|
84
122
|
|
|
85
123
|
# Arcsecond beat, to launch specific background tasks at specific times.
|
|
86
124
|
beat:
|
|
@@ -114,6 +152,23 @@ services:
|
|
|
114
152
|
condition: service_started
|
|
115
153
|
ports:
|
|
116
154
|
- "5555:5555"
|
|
155
|
+
environment:
|
|
156
|
+
# Download limits: how many downloads one computer may have open at
|
|
157
|
+
# once, how many in total, and the speed of each (bytes per second,
|
|
158
|
+
# k and m allowed, 0 for none). They keep downloads from filling the
|
|
159
|
+
# link a remote observer drives the telescope through.
|
|
160
|
+
ARCSECOND_DOWNLOADS_PER_CLIENT: ${ARCSECOND_DOWNLOADS_PER_CLIENT:-6}
|
|
161
|
+
ARCSECOND_DOWNLOADS_TOTAL: ${ARCSECOND_DOWNLOADS_TOTAL:-24}
|
|
162
|
+
ARCSECOND_DOWNLOAD_RATE: ${ARCSECOND_DOWNLOAD_RATE:-20m}
|
|
163
|
+
volumes:
|
|
164
|
+
# The same folder, read-only: this container sends the stored files
|
|
165
|
+
# itself, once the backend has said who may read them.
|
|
166
|
+
- type: bind
|
|
167
|
+
source: ${SHARED_DATA_PATH}
|
|
168
|
+
target: /data
|
|
169
|
+
read_only: true
|
|
170
|
+
bind:
|
|
171
|
+
create_host_path: true
|
|
117
172
|
|
|
118
173
|
# >>> arcsecond:alerts
|
|
119
174
|
# Arcsecond transient-alerts consumer (optional).
|
|
@@ -272,7 +272,7 @@ def status(directory):
|
|
|
272
272
|
@basic_options
|
|
273
273
|
def logs(directory, service, follow, tail):
|
|
274
274
|
"""Show recent logs. Services are named as in `arcsecond status`:
|
|
275
|
-
backend, worker, beat, web, db, broker, platesolver, alerts."""
|
|
275
|
+
backend, worker, dataworker, beat, web, db, broker, platesolver, alerts."""
|
|
276
276
|
install = stack.resolve_install_dir(directory)
|
|
277
277
|
stack.ensure_docker()
|
|
278
278
|
args = ["logs", "--tail", str(tail)]
|
|
@@ -4,7 +4,8 @@ From the operator's side there is one fact: Arcsecond sent a token, and it
|
|
|
4
4
|
has to be entered once so the machine can download Arcsecond.local. What it
|
|
5
5
|
is underneath — a login to the container registry the images are pulled
|
|
6
6
|
from, ghcr.io, as the organisation user — is this module's business, not
|
|
7
|
-
theirs, and the words "registry" and "docker" stay out of what they read
|
|
7
|
+
theirs, and the words "registry" and "docker" stay out of what they read —
|
|
8
|
+
except when Docker's own password store is what failed, which only they can fix.
|
|
8
9
|
|
|
9
10
|
The token is asked for without echo, or read from standard input for a
|
|
10
11
|
script, and handed to Docker over a pipe. There is deliberately no
|
|
@@ -96,7 +97,10 @@ def store_token(token: str) -> None:
|
|
|
96
97
|
token=token + "\n",
|
|
97
98
|
)
|
|
98
99
|
if result.returncode != 0:
|
|
99
|
-
|
|
100
|
+
output = (result.stderr or result.stdout or "").strip()
|
|
101
|
+
if _is_credential_store_failure(output):
|
|
102
|
+
raise ArcsecondError(_credential_store_advice(output))
|
|
103
|
+
detail = output.splitlines()
|
|
100
104
|
reason = detail[-1] if detail else "Docker gave no reason"
|
|
101
105
|
raise ArcsecondError(
|
|
102
106
|
f"The token was refused: {reason}\n"
|
|
@@ -105,6 +109,43 @@ def store_token(token: str) -> None:
|
|
|
105
109
|
)
|
|
106
110
|
|
|
107
111
|
|
|
112
|
+
# Docker only saves the credential once the registry has accepted it, so these
|
|
113
|
+
# words mean the token was good and the machine's own keychain failed. Docker
|
|
114
|
+
# Desktop on Linux keeps credentials in `pass`, which is empty until someone
|
|
115
|
+
# runs `pass init` — it says so in its install notes, and nobody reads them.
|
|
116
|
+
_CREDENTIAL_STORE_FAILURES = ("error saving credentials", "error storing credentials")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _is_credential_store_failure(output: str) -> bool:
|
|
120
|
+
lowered = output.lower()
|
|
121
|
+
return any(words in lowered for words in _CREDENTIAL_STORE_FAILURES)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _credential_store_advice(output: str) -> str:
|
|
125
|
+
lowered = output.lower()
|
|
126
|
+
head = (
|
|
127
|
+
"The token is good — Arcsecond accepted it — but this machine could not "
|
|
128
|
+
"save it: Docker keeps it in a password store that refused to write.\n"
|
|
129
|
+
)
|
|
130
|
+
if "pass init" in lowered or "pass not initialized" in lowered:
|
|
131
|
+
return head + (
|
|
132
|
+
"Docker Desktop on Linux uses `pass`, and it has not been set up yet. "
|
|
133
|
+
"Set it up once, then run `arcsecond token set` again:\n"
|
|
134
|
+
" gpg --generate-key (any name and e-mail; note the key id it prints)\n"
|
|
135
|
+
" pass init <that key id>\n"
|
|
136
|
+
"Or, if this machine runs Docker Engine without Docker Desktop, remove the "
|
|
137
|
+
f'"credsStore" line from {docker_config_path()} and Docker will keep the '
|
|
138
|
+
"token in that file instead."
|
|
139
|
+
)
|
|
140
|
+
detail = output.splitlines()[-1] if output else ""
|
|
141
|
+
return head + (
|
|
142
|
+
f"It said: {detail}\n"
|
|
143
|
+
'Unlock or repair that store, or remove the "credsStore" line from '
|
|
144
|
+
f"{docker_config_path()} so Docker keeps the token in that file, then run "
|
|
145
|
+
"`arcsecond token set` again."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
108
149
|
def prompt_for_token() -> str:
|
|
109
150
|
return click.prompt(
|
|
110
151
|
"Access token from Arcsecond (nothing is shown while you type)", hide_input=True
|
|
@@ -328,3 +328,20 @@ def test_failed_recreate_reports_but_keeps_the_new_password(tmp_path, monkeypatc
|
|
|
328
328
|
# The rotation itself succeeded, so the file must keep the new value.
|
|
329
329
|
assert database._read_env_value("POSTGRES_PASSWORD", env_path) == cluster.password
|
|
330
330
|
assert "password was changed successfully" in result.output
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def test_the_data_worker_is_recreated_only_where_the_compose_file_has_it(tmp_path, monkeypatch):
|
|
334
|
+
"""docker-compose.yml 7.3 added it; naming it on an older install would
|
|
335
|
+
make `docker compose up` fail outright."""
|
|
336
|
+
monkeypatch.chdir(tmp_path)
|
|
337
|
+
compose = Path(tmp_path) / "docker-compose.yml"
|
|
338
|
+
|
|
339
|
+
compose.write_text("services:\n worker:\n container_name: arcsecond-worker\n", encoding="utf-8")
|
|
340
|
+
assert database._services_to_recreate() == ["backend", "worker", "beat"]
|
|
341
|
+
|
|
342
|
+
compose.write_text(
|
|
343
|
+
"services:\n dataworker:\n container_name: arcsecond-dataworker\n"
|
|
344
|
+
" # >>> arcsecond:alerts\n alerts:\n # <<< arcsecond:alerts\n",
|
|
345
|
+
encoding="utf-8",
|
|
346
|
+
)
|
|
347
|
+
assert database._services_to_recreate() == ["backend", "worker", "beat", "dataworker", "alerts"]
|
|
@@ -65,6 +65,42 @@ def test_a_refused_token_is_explained_without_docker_words(docker):
|
|
|
65
65
|
assert "registry" not in result.output.lower()
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
def _login_fails_with(monkeypatch, stderr):
|
|
69
|
+
def fake_run(cmd, input=None, **kwargs):
|
|
70
|
+
return subprocess.CompletedProcess(cmd, 1, stdout="", stderr=stderr)
|
|
71
|
+
|
|
72
|
+
monkeypatch.setattr(token.subprocess, "run", fake_run)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_an_uninitialised_pass_store_is_not_a_refused_token(docker, monkeypatch):
|
|
76
|
+
_login_fails_with(
|
|
77
|
+
monkeypatch,
|
|
78
|
+
"Error saving credentials: error storing credentials - err: exit status 1, "
|
|
79
|
+
'out: `pass not initialized: exit status 1: Error: password store is empty. Try "pass init".`',
|
|
80
|
+
)
|
|
81
|
+
result = _invoke("token", "set", input="ghp_good\n")
|
|
82
|
+
assert result.exit_code == 1
|
|
83
|
+
assert (
|
|
84
|
+
"token is good" in result.output
|
|
85
|
+
and "refused" not in result.output.split("\n")[0]
|
|
86
|
+
)
|
|
87
|
+
assert "pass init" in result.output and "gpg --generate-key" in result.output
|
|
88
|
+
assert "team@arcsecond.io" not in result.output
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_any_other_credential_store_failure_names_the_config(
|
|
92
|
+
docker, monkeypatch, tmp_path
|
|
93
|
+
):
|
|
94
|
+
_login_fails_with(
|
|
95
|
+
monkeypatch,
|
|
96
|
+
"Error saving credentials: error storing credentials - err: exit status 1, out: `keychain locked`",
|
|
97
|
+
)
|
|
98
|
+
result = _invoke("token", "set", input="ghp_good\n")
|
|
99
|
+
assert result.exit_code == 1
|
|
100
|
+
assert "token is good" in result.output and "keychain locked" in result.output
|
|
101
|
+
assert "credsStore" in result.output and "gpg" not in result.output
|
|
102
|
+
|
|
103
|
+
|
|
68
104
|
def test_chevrons_and_empty_input_never_reach_docker(docker):
|
|
69
105
|
assert "placeholder" in _invoke("token", "set", input="<ghp_good>\n").output
|
|
70
106
|
assert _invoke("token", "set", "--stdin", input="\n").exit_code == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{arcsecond-4.2.1 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_full_process.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|