arcsecond 4.3.0__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.
Files changed (121) hide show
  1. {arcsecond-4.3.0 → arcsecond-4.3.1}/PKG-INFO +1 -1
  2. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/token.py +43 -2
  3. {arcsecond-4.3.0 → arcsecond-4.3.1}/pyproject.toml +1 -1
  4. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_token.py +36 -0
  5. {arcsecond-4.3.0 → arcsecond-4.3.1}/uv.lock +1 -1
  6. {arcsecond-4.3.0 → arcsecond-4.3.1}/.docker/Dockerfile_postgres +0 -0
  7. {arcsecond-4.3.0 → arcsecond-4.3.1}/.docker/Dockerfile_redis +0 -0
  8. {arcsecond-4.3.0 → arcsecond-4.3.1}/.flake8 +0 -0
  9. {arcsecond-4.3.0 → arcsecond-4.3.1}/.gitattributes +0 -0
  10. {arcsecond-4.3.0 → arcsecond-4.3.1}/.github/dependabot.yml +0 -0
  11. {arcsecond-4.3.0 → arcsecond-4.3.1}/.github/workflows/pythonpublish.yml +0 -0
  12. {arcsecond-4.3.0 → arcsecond-4.3.1}/.github/workflows/tests.yml +0 -0
  13. {arcsecond-4.3.0 → arcsecond-4.3.1}/.gitignore +0 -0
  14. {arcsecond-4.3.0 → arcsecond-4.3.1}/LICENSE +0 -0
  15. {arcsecond-4.3.0 → arcsecond-4.3.1}/Makefile +0 -0
  16. {arcsecond-4.3.0 → arcsecond-4.3.1}/README.md +0 -0
  17. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/__init__.py +0 -0
  18. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/__version__.py +0 -0
  19. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/alpaca/__init__.py +0 -0
  20. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/alpaca/commands.py +0 -0
  21. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/alpaca/dome_probe.py +0 -0
  22. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/alpaca/probe.py +0 -0
  23. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/alpaca/telescope_probe.py +0 -0
  24. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/api/__init__.py +0 -0
  25. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/api/config.py +0 -0
  26. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/api/constants.py +0 -0
  27. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/api/endpoint.py +0 -0
  28. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/api/main.py +0 -0
  29. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/api/resources.py +0 -0
  30. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cli.py +0 -0
  31. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/__init__.py +0 -0
  32. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/auth.py +0 -0
  33. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/resources.py +0 -0
  34. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/__init__.py +0 -0
  35. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/constants.py +0 -0
  36. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/context.py +0 -0
  37. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
  38. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
  39. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
  40. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
  41. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
  42. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/errors.py +0 -0
  43. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/logger.py +0 -0
  44. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/uploader.py +0 -0
  45. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/utils.py +0 -0
  46. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploader/walker.py +0 -0
  47. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/cloud/uploads.py +0 -0
  48. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/docgen.py +0 -0
  49. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/errors.py +0 -0
  50. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/__init__.py +0 -0
  51. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/backups.py +0 -0
  52. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/check.py +0 -0
  53. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/composedoc.py +0 -0
  54. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/database.py +0 -0
  55. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/docker/__init__.py +0 -0
  56. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/docker/docker-compose.yml +0 -0
  57. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/lifecycle.py +0 -0
  58. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/local.py +0 -0
  59. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/stack.py +0 -0
  60. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/hosting/utils.py +0 -0
  61. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/__init__.py +0 -0
  62. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/autostart.py +0 -0
  63. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/commands.py +0 -0
  64. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/detection.py +0 -0
  65. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/proxy.py +0 -0
  66. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/registry.py +0 -0
  67. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/runtime.py +0 -0
  68. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/sources/__init__.py +0 -0
  69. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/sources/base.py +0 -0
  70. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/sources/filewatch.py +0 -0
  71. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/sources/mdns.py +0 -0
  72. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/sources/network.py +0 -0
  73. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/sources/opencv.py +0 -0
  74. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/imagesources/store.py +0 -0
  75. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/options.py +0 -0
  76. {arcsecond-4.3.0 → arcsecond-4.3.1}/arcsecond/targets.py +0 -0
  77. {arcsecond-4.3.0 → arcsecond-4.3.1}/examples/example_upload_files.py +0 -0
  78. {arcsecond-4.3.0 → arcsecond-4.3.1}/requirements.txt +0 -0
  79. {arcsecond-4.3.0 → arcsecond-4.3.1}/setup.cfg +0 -0
  80. {arcsecond-4.3.0 → arcsecond-4.3.1}/setup.py +0 -0
  81. {arcsecond-4.3.0 → arcsecond-4.3.1}/sonar-project.properties +0 -0
  82. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/__init__.py +0 -0
  83. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/api/__init__.py +0 -0
  84. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/api/test_api.py +0 -0
  85. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/api/test_api_endpoint.py +0 -0
  86. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/api/test_config.py +0 -0
  87. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/api/test_config_pointer.py +0 -0
  88. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/api/test_targets.py +0 -0
  89. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/__init__.py +0 -0
  90. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/__init__.py +0 -0
  91. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/__init__.py +0 -0
  92. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
  93. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
  94. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
  95. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
  96. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
  97. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/conftest.py +0 -0
  98. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/fixtures/file1.fits +0 -0
  99. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_alpaca_probe.py +0 -0
  100. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_cli.py +0 -0
  101. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_cli_api_commands.py +0 -0
  102. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_docgen.py +0 -0
  103. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_backups_install_dir.py +0 -0
  104. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_backups_restore.py +0 -0
  105. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_check.py +0 -0
  106. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_composedoc.py +0 -0
  107. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_database.py +0 -0
  108. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_lifecycle.py +0 -0
  109. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_local.py +0 -0
  110. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_stack.py +0 -0
  111. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_hosting_utils_secret_key.py +0 -0
  112. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_autostart.py +0 -0
  113. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_commands.py +0 -0
  114. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_detection.py +0 -0
  115. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_mdns.py +0 -0
  116. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_network.py +0 -0
  117. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_proxy.py +0 -0
  118. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_store.py +0 -0
  119. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_imagesources_webcam.py +0 -0
  120. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/test_targets_planning.py +0 -0
  121. {arcsecond-4.3.0 → arcsecond-4.3.1}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arcsecond
3
- Version: 4.3.0
3
+ Version: 4.3.1
4
4
  Summary: CLI for arcsecond.io
5
5
  Project-URL: Homepage, https://github.com/arcsecond-io/cli
6
6
  Project-URL: Issues, https://github.com/arcsecond-io/cli/issues
@@ -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
- detail = (result.stderr or result.stdout or "").strip().splitlines()
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arcsecond"
7
- version = "4.3.0"
7
+ version = "4.3.1"
8
8
  authors = [
9
9
  { name = "Cedric Foellmi", email = "cedric@arcsecond.io" },
10
10
  ]
@@ -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
@@ -155,7 +155,7 @@ wheels = [
155
155
 
156
156
  [[package]]
157
157
  name = "arcsecond"
158
- version = "4.3.0"
158
+ version = "4.3.1"
159
159
  source = { editable = "." }
160
160
  dependencies = [
161
161
  { name = "alpyca" },
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