arcsecond 4.3.1__tar.gz → 4.3.3__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.3.1 → arcsecond-4.3.3}/PKG-INFO +1 -1
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/api/endpoint.py +10 -1
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/api/resources.py +1 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/stack.py +28 -2
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/token.py +15 -8
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/targets.py +7 -8
- {arcsecond-4.3.1 → arcsecond-4.3.3}/pyproject.toml +1 -1
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/api/test_config.py +16 -6
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_database.py +13 -3
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_token.py +72 -1
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_targets_planning.py +5 -2
- {arcsecond-4.3.1 → arcsecond-4.3.3}/uv.lock +1 -1
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.docker/Dockerfile_postgres +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.docker/Dockerfile_redis +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.flake8 +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.gitattributes +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.github/dependabot.yml +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.github/workflows/pythonpublish.yml +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.github/workflows/tests.yml +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/.gitignore +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/LICENSE +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/Makefile +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/README.md +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/__version__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/alpaca/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/alpaca/commands.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/alpaca/dome_probe.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/alpaca/probe.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/alpaca/telescope_probe.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/api/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/api/config.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/api/constants.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/api/main.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cli.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/auth.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/resources.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/constants.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/context.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/errors.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/logger.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/uploader.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/utils.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploader/walker.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/cloud/uploads.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/docgen.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/errors.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/backups.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/check.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/composedoc.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/database.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/docker/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/docker/docker-compose.yml +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/lifecycle.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/local.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/hosting/utils.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/autostart.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/commands.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/detection.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/proxy.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/registry.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/runtime.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/sources/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/sources/base.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/sources/filewatch.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/sources/mdns.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/sources/network.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/sources/opencv.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/imagesources/store.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/arcsecond/options.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/examples/example_upload_files.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/requirements.txt +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/setup.cfg +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/setup.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/sonar-project.properties +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/api/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/api/test_api.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/api/test_api_endpoint.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/api/test_config_pointer.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/api/test_targets.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/conftest.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/fixtures/file1.fits +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_alpaca_probe.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_cli.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_cli_api_commands.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_docgen.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_backups_install_dir.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_backups_restore.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_check.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_composedoc.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_lifecycle.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_local.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_stack.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_hosting_utils_secret_key.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_autostart.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_commands.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_detection.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_mdns.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_network.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_proxy.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_store.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/test_imagesources_webcam.py +0 -0
- {arcsecond-4.3.1 → arcsecond-4.3.3}/tests/utils.py +0 -0
|
@@ -8,7 +8,16 @@ from arcsecond.api.constants import API_AUTH_PATH_VERIFY, API_AUTH_PATH_VERIFY_P
|
|
|
8
8
|
from arcsecond.errors import ArcsecondError
|
|
9
9
|
|
|
10
10
|
SAFE_METHODS = ["GET", "OPTIONS"]
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
# The portal roles allowed to write. Both "owner" and "superadmin" are listed
|
|
13
|
+
# because they are the same role: the backend renamed "superadmin" to "owner",
|
|
14
|
+
# and this tool talks to installations on either side of that update.
|
|
15
|
+
# Observatories update the tool independently of their installation, so a
|
|
16
|
+
# freshly updated tool can still be pointed at an installation that has not
|
|
17
|
+
# been updated yet and answers "superadmin".
|
|
18
|
+
#
|
|
19
|
+
# "superadmin" can go once no supported installation predates the rename.
|
|
20
|
+
WRITABLE_MEMBERSHIPS = ["owner", "superadmin", "admin", "member"]
|
|
12
21
|
|
|
13
22
|
|
|
14
23
|
class ArcsecondAPIEndpoint(object):
|
|
@@ -229,6 +229,22 @@ def compose_streaming(
|
|
|
229
229
|
)
|
|
230
230
|
|
|
231
231
|
|
|
232
|
+
REGISTRY_REFUSED = (
|
|
233
|
+
"Docker could not download an Arcsecond image: the registry refused.\n"
|
|
234
|
+
"Enter the token Arcsecond gave your observatory: arcsecond token set"
|
|
235
|
+
)
|
|
236
|
+
REGISTRY_REFUSED_NO_TOKEN = (
|
|
237
|
+
"Docker could not download an Arcsecond image: this machine has no token "
|
|
238
|
+
"saved, so it asked without one.\n"
|
|
239
|
+
"Enter the token Arcsecond gave your observatory: arcsecond token set\n"
|
|
240
|
+
'It must answer "Token accepted." before Arcsecond.local can be downloaded.'
|
|
241
|
+
)
|
|
242
|
+
REGISTRY_REFUSED_WITH_TOKEN = (
|
|
243
|
+
"Docker could not download an Arcsecond image: the token this machine holds "
|
|
244
|
+
"was refused. It may have expired or been replaced — enter the current one "
|
|
245
|
+
"with arcsecond token set, or ask team@arcsecond.io for a new one."
|
|
246
|
+
)
|
|
247
|
+
|
|
232
248
|
# The compose failures the troubleshooting page used to explain, matched on
|
|
233
249
|
# the daemon's own words so `start` can answer them on the spot.
|
|
234
250
|
_KNOWN_FAILURES = (
|
|
@@ -267,8 +283,7 @@ _KNOWN_FAILURES = (
|
|
|
267
283
|
),
|
|
268
284
|
(
|
|
269
285
|
("denied", "unauthorized", "pull access denied"),
|
|
270
|
-
|
|
271
|
-
"Enter the token Arcsecond gave your observatory: arcsecond token set",
|
|
286
|
+
REGISTRY_REFUSED,
|
|
272
287
|
),
|
|
273
288
|
)
|
|
274
289
|
|
|
@@ -284,6 +299,17 @@ def explain_compose_failure(stderr: str) -> Optional[str]:
|
|
|
284
299
|
|
|
285
300
|
def raise_compose_failure(what: str, result: subprocess.CompletedProcess) -> None:
|
|
286
301
|
explanation = explain_compose_failure(result.stderr)
|
|
302
|
+
if explanation == REGISTRY_REFUSED:
|
|
303
|
+
# "Unauthorized" reads the same whether the token is wrong or was never
|
|
304
|
+
# saved (a password store that could not write it, a first `start`
|
|
305
|
+
# before `token set`); the two call for different words.
|
|
306
|
+
from . import token # token imports this module
|
|
307
|
+
|
|
308
|
+
explanation = (
|
|
309
|
+
REGISTRY_REFUSED_WITH_TOKEN
|
|
310
|
+
if token.has_token()
|
|
311
|
+
else REGISTRY_REFUSED_NO_TOKEN
|
|
312
|
+
)
|
|
287
313
|
lines = [f"`{what}` failed (exit code {result.returncode})."]
|
|
288
314
|
if explanation:
|
|
289
315
|
lines += ["", explanation]
|
|
@@ -53,21 +53,23 @@ def docker_config_path() -> Path:
|
|
|
53
53
|
def has_token() -> bool:
|
|
54
54
|
"""Whether Docker on this machine holds a credential for the images.
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
When the config names a credential store (Docker Desktop's, `pass`, the OS
|
|
57
|
+
keychain), the store is the truth and is asked: the empty entry Docker
|
|
58
|
+
leaves under `auths` survives a store being emptied or replaced, and a
|
|
59
|
+
machine whose `pass` was never initialised can carry one from an older
|
|
60
|
+
login. Without a store, the secret sits inline under `auths`. Only when
|
|
61
|
+
the store cannot answer — its helper missing, or waiting on a passphrase
|
|
62
|
+
prompt nobody sees — does the entry's presence stand in for it.
|
|
60
63
|
"""
|
|
61
64
|
try:
|
|
62
65
|
config = json.loads(docker_config_path().read_text(encoding="utf-8") or "{}")
|
|
63
66
|
except (OSError, ValueError):
|
|
64
67
|
return False
|
|
65
68
|
auths = config.get("auths") or {}
|
|
66
|
-
|
|
67
|
-
return True
|
|
69
|
+
entries = [value for key, value in auths.items() if REGISTRY in key]
|
|
68
70
|
store = (config.get("credHelpers") or {}).get(REGISTRY) or config.get("credsStore")
|
|
69
71
|
if not store:
|
|
70
|
-
return
|
|
72
|
+
return any(isinstance(entry, dict) and entry.get("auth") for entry in entries)
|
|
71
73
|
try:
|
|
72
74
|
probe = subprocess.run(
|
|
73
75
|
[f"docker-credential-{store}", "get"],
|
|
@@ -78,8 +80,13 @@ def has_token() -> bool:
|
|
|
78
80
|
timeout=15,
|
|
79
81
|
)
|
|
80
82
|
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
83
|
+
return bool(entries)
|
|
84
|
+
if probe.returncode != 0:
|
|
85
|
+
return False
|
|
86
|
+
try:
|
|
87
|
+
return bool(json.loads(probe.stdout or "{}").get("Secret"))
|
|
88
|
+
except ValueError:
|
|
81
89
|
return False
|
|
82
|
-
return probe.returncode == 0 and REGISTRY_USER in (probe.stdout or "")
|
|
83
90
|
|
|
84
91
|
|
|
85
92
|
def store_token(token: str) -> None:
|
|
@@ -116,9 +116,12 @@ def _plan_manual(
|
|
|
116
116
|
) -> "ArcsecondTargetPayloadPlan":
|
|
117
117
|
"""The plan for coordinates the user gave us themselves.
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
They go as the target's own `coordinates`, which take precedence over the
|
|
120
|
+
catalogue's — offset or not, the user is assumed right — and need no
|
|
121
|
+
object: the backend names one after the fact when the catalogue knows the
|
|
122
|
+
name. Only an 'AstronomicalObject' may carry them, so that class is
|
|
123
|
+
assumed here and anything inferred is set aside — with a warning, since
|
|
124
|
+
silently ignoring what was inferred would be worse.
|
|
122
125
|
"""
|
|
123
126
|
if user_target_class and user_target_class != TARGET_CLASS_ASTRONOMICAL_OBJECT:
|
|
124
127
|
errors.append(
|
|
@@ -142,11 +145,7 @@ def _plan_manual(
|
|
|
142
145
|
errors.append("Manual coordinates require a target name.")
|
|
143
146
|
|
|
144
147
|
payload["target_class"] = effective_target_class
|
|
145
|
-
payload["
|
|
146
|
-
payload["object"] = {
|
|
147
|
-
"name": effective_name or effective_identifier,
|
|
148
|
-
"equatorial_coordinates": effective_coordinates,
|
|
149
|
-
}
|
|
148
|
+
payload["coordinates"] = effective_coordinates
|
|
150
149
|
|
|
151
150
|
return ArcsecondTargetPayloadPlan(
|
|
152
151
|
payload=payload,
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from arcsecond.api.config import CONFIG_DIR_ENV_VAR, ArcsecondConfig
|
|
7
|
+
from arcsecond.api.endpoint import WRITABLE_MEMBERSHIPS
|
|
7
8
|
from arcsecond.errors import ArcsecondError
|
|
8
9
|
from arcsecond.options import State
|
|
9
10
|
from tests.utils import random_string, save_test_credentials
|
|
@@ -145,11 +146,22 @@ def test_config_memberships():
|
|
|
145
146
|
config = ArcsecondConfig(api_name=random_api_name)
|
|
146
147
|
assert config.memberships == {}
|
|
147
148
|
ms = [
|
|
148
|
-
{"organisation": "oma", "role": "
|
|
149
|
+
{"organisation": "oma", "role": "owner"},
|
|
149
150
|
{"organisation": "arcsecond", "role": "member"},
|
|
150
151
|
]
|
|
151
152
|
config.save_memberships(ms)
|
|
152
|
-
assert config.memberships == {"oma": "
|
|
153
|
+
assert config.memberships == {"oma": "owner", "arcsecond": "member"}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_owner_membership_is_writable():
|
|
157
|
+
assert "owner" in WRITABLE_MEMBERSHIPS
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_superadmin_membership_is_still_writable():
|
|
161
|
+
# An installation that predates the rename of "superadmin" to "owner"
|
|
162
|
+
# still answers with the old name, and the tool must keep treating it as
|
|
163
|
+
# the same role until no supported installation does.
|
|
164
|
+
assert "superadmin" in WRITABLE_MEMBERSHIPS
|
|
153
165
|
|
|
154
166
|
|
|
155
167
|
def test_config_access_key():
|
|
@@ -192,10 +204,8 @@ def test_default_logged_in_state():
|
|
|
192
204
|
def test_default_logged_in_with_membership_state():
|
|
193
205
|
random_api_name = random_string()
|
|
194
206
|
save_test_credentials(
|
|
195
|
-
random_api_name, "cedric", [{"organisation": "saao", "role": "
|
|
207
|
+
random_api_name, "cedric", [{"organisation": "saao", "role": "owner"}]
|
|
196
208
|
)
|
|
197
209
|
assert ArcsecondConfig(api_name=random_api_name).is_logged_in is True
|
|
198
210
|
assert ArcsecondConfig(api_name=random_api_name).username == "cedric"
|
|
199
|
-
assert ArcsecondConfig(api_name=random_api_name).memberships == {
|
|
200
|
-
"saao": "superadmin"
|
|
201
|
-
}
|
|
211
|
+
assert ArcsecondConfig(api_name=random_api_name).memberships == {"saao": "owner"}
|
|
@@ -330,13 +330,17 @@ def test_failed_recreate_reports_but_keeps_the_new_password(tmp_path, monkeypatc
|
|
|
330
330
|
assert "password was changed successfully" in result.output
|
|
331
331
|
|
|
332
332
|
|
|
333
|
-
def test_the_data_worker_is_recreated_only_where_the_compose_file_has_it(
|
|
333
|
+
def test_the_data_worker_is_recreated_only_where_the_compose_file_has_it(
|
|
334
|
+
tmp_path, monkeypatch
|
|
335
|
+
):
|
|
334
336
|
"""docker-compose.yml 7.3 added it; naming it on an older install would
|
|
335
337
|
make `docker compose up` fail outright."""
|
|
336
338
|
monkeypatch.chdir(tmp_path)
|
|
337
339
|
compose = Path(tmp_path) / "docker-compose.yml"
|
|
338
340
|
|
|
339
|
-
compose.write_text(
|
|
341
|
+
compose.write_text(
|
|
342
|
+
"services:\n worker:\n container_name: arcsecond-worker\n", encoding="utf-8"
|
|
343
|
+
)
|
|
340
344
|
assert database._services_to_recreate() == ["backend", "worker", "beat"]
|
|
341
345
|
|
|
342
346
|
compose.write_text(
|
|
@@ -344,4 +348,10 @@ def test_the_data_worker_is_recreated_only_where_the_compose_file_has_it(tmp_pat
|
|
|
344
348
|
" # >>> arcsecond:alerts\n alerts:\n # <<< arcsecond:alerts\n",
|
|
345
349
|
encoding="utf-8",
|
|
346
350
|
)
|
|
347
|
-
assert database._services_to_recreate() == [
|
|
351
|
+
assert database._services_to_recreate() == [
|
|
352
|
+
"backend",
|
|
353
|
+
"worker",
|
|
354
|
+
"beat",
|
|
355
|
+
"dataworker",
|
|
356
|
+
"alerts",
|
|
357
|
+
]
|
|
@@ -7,6 +7,7 @@ import pytest
|
|
|
7
7
|
from click.testing import CliRunner
|
|
8
8
|
|
|
9
9
|
from arcsecond import cli
|
|
10
|
+
from arcsecond.errors import ArcsecondError
|
|
10
11
|
from arcsecond.hosting import local, stack, token
|
|
11
12
|
|
|
12
13
|
|
|
@@ -117,12 +118,61 @@ def test_status_reads_dockers_config(docker, tmp_path):
|
|
|
117
118
|
result = _invoke("token")
|
|
118
119
|
assert result.exit_code == 1 and "no token yet" in result.output
|
|
119
120
|
(tmp_path / "config.json").write_text(
|
|
120
|
-
json.dumps({"auths": {"ghcr.io": {
|
|
121
|
+
json.dumps({"auths": {"ghcr.io": {"auth": "YXJjc2Vjb25kLWlvOng="}}})
|
|
121
122
|
)
|
|
122
123
|
result = _invoke("token")
|
|
123
124
|
assert result.exit_code == 0 and "has the token" in result.output
|
|
124
125
|
|
|
125
126
|
|
|
127
|
+
def _store_answers(monkeypatch, tmp_path, config, returncode, stdout=""):
|
|
128
|
+
monkeypatch.setattr(token, "docker_config_path", lambda: tmp_path / "config.json")
|
|
129
|
+
(tmp_path / "config.json").write_text(json.dumps(config))
|
|
130
|
+
|
|
131
|
+
def fake_run(cmd, input=None, **kwargs):
|
|
132
|
+
return subprocess.CompletedProcess(cmd, returncode, stdout=stdout)
|
|
133
|
+
|
|
134
|
+
monkeypatch.setattr(token.subprocess, "run", fake_run)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_an_empty_entry_is_not_a_token_when_the_store_holds_nothing(
|
|
138
|
+
monkeypatch, tmp_path
|
|
139
|
+
):
|
|
140
|
+
# JB's machine: `pass` never initialised, an `auths` entry left behind.
|
|
141
|
+
_store_answers(
|
|
142
|
+
monkeypatch,
|
|
143
|
+
tmp_path,
|
|
144
|
+
{"auths": {"ghcr.io": {}}, "credsStore": "desktop"},
|
|
145
|
+
returncode=1,
|
|
146
|
+
stdout="credentials not found in native keychain",
|
|
147
|
+
)
|
|
148
|
+
assert token.has_token() is False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_a_token_saved_under_another_username_still_counts(monkeypatch, tmp_path):
|
|
152
|
+
# Before 4.2 the documentation had people log in under their own name.
|
|
153
|
+
_store_answers(
|
|
154
|
+
monkeypatch,
|
|
155
|
+
tmp_path,
|
|
156
|
+
{"credsStore": "pass"},
|
|
157
|
+
returncode=0,
|
|
158
|
+
stdout=json.dumps({"Username": "jb", "Secret": "x"}),
|
|
159
|
+
)
|
|
160
|
+
assert token.has_token() is True
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_a_store_that_cannot_answer_falls_back_on_the_entry(monkeypatch, tmp_path):
|
|
164
|
+
monkeypatch.setattr(token, "docker_config_path", lambda: tmp_path / "config.json")
|
|
165
|
+
(tmp_path / "config.json").write_text(
|
|
166
|
+
json.dumps({"auths": {"ghcr.io": {}}, "credsStore": "pass"})
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def hangs(cmd, **kwargs):
|
|
170
|
+
raise subprocess.TimeoutExpired(cmd, 15)
|
|
171
|
+
|
|
172
|
+
monkeypatch.setattr(token.subprocess, "run", hangs)
|
|
173
|
+
assert token.has_token() is True
|
|
174
|
+
|
|
175
|
+
|
|
126
176
|
def test_has_token_asks_the_credential_store_when_the_config_lists_nothing(
|
|
127
177
|
monkeypatch, tmp_path
|
|
128
178
|
):
|
|
@@ -150,6 +200,27 @@ def test_start_points_at_the_token_when_the_pull_is_denied():
|
|
|
150
200
|
)
|
|
151
201
|
|
|
152
202
|
|
|
203
|
+
@pytest.mark.parametrize(
|
|
204
|
+
"saved, says, never",
|
|
205
|
+
[
|
|
206
|
+
(False, "no token saved", "expired"),
|
|
207
|
+
(True, "was refused", "no token saved"),
|
|
208
|
+
],
|
|
209
|
+
)
|
|
210
|
+
def test_start_tells_a_missing_token_from_a_refused_one(
|
|
211
|
+
monkeypatch, saved, says, never
|
|
212
|
+
):
|
|
213
|
+
monkeypatch.setattr(token, "has_token", lambda: saved)
|
|
214
|
+
result = subprocess.CompletedProcess(
|
|
215
|
+
[], 1, stderr="Error response from daemon: error from registry: unauthorized"
|
|
216
|
+
)
|
|
217
|
+
with pytest.raises(ArcsecondError) as excinfo:
|
|
218
|
+
stack.raise_compose_failure("docker compose up", result)
|
|
219
|
+
message = str(excinfo.value)
|
|
220
|
+
assert says in message and never not in message
|
|
221
|
+
assert "arcsecond token set" in message
|
|
222
|
+
|
|
223
|
+
|
|
153
224
|
# --- setup asks for it -----------------------------------------------------------
|
|
154
225
|
|
|
155
226
|
|
|
@@ -13,11 +13,14 @@ def test_plan_target_payload_uses_manual_astroobject_when_coordinates_are_provid
|
|
|
13
13
|
assert plan.target_class == "AstronomicalObject"
|
|
14
14
|
assert plan.mode == "manual"
|
|
15
15
|
assert plan.payload["target_class"] == "AstronomicalObject"
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
# The user's coordinates are the target's own; no object is sent, the
|
|
17
|
+
# backend names one when the catalogue knows the name.
|
|
18
|
+
assert plan.payload["coordinates"] == {
|
|
18
19
|
"right_ascension": 344.366,
|
|
19
20
|
"declination": 20.768,
|
|
20
21
|
}
|
|
22
|
+
assert "mode" not in plan.payload
|
|
23
|
+
assert "object" not in plan.payload
|
|
21
24
|
assert "ignored" in plan.warnings[0]
|
|
22
25
|
|
|
23
26
|
|
|
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.3.1 → arcsecond-4.3.3}/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
|