devpi-server 6.12.1__tar.gz → 6.13.0__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.
- {devpi-server-6.12.1 → devpi-server-6.13.0}/.flake8 +2 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/CHANGELOG +35 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/PKG-INFO +37 -10
- devpi-server-6.13.0/devpi_server/__init__.py +1 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/config.py +186 -77
- devpi-server-6.13.0/devpi_server/filestore.py +630 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/fileutil.py +2 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/fsck.py +7 -9
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/genconfig.py +3 -3
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/hookspecs.py +4 -4
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/importexport.py +40 -26
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/init.py +7 -7
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/keyfs.py +46 -23
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/log.py +27 -22
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/main.py +38 -19
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/mirror.py +7 -7
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/model.py +61 -19
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/mythread.py +37 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/passwd.py +4 -4
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/replica.py +181 -94
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/sizeof.py +1 -1
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/views.py +94 -49
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/PKG-INFO +37 -10
- devpi-server-6.13.0/pyproject.toml +99 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/setup.py +2 -1
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/functional.py +8 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/plugin.py +172 -91
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_config.py +93 -22
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_conftest.py +24 -3
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_filestore.py +16 -20
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_fsck.py +5 -8
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_importexport.py +98 -81
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_keyfs.py +14 -12
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_keyfs_sqlite_fs.py +6 -7
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_main.py +35 -31
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_mirror.py +28 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_model.py +5 -3
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_mythread.py +4 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_nginx_replica.py +5 -4
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_permissions.py +7 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_replica.py +131 -77
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_replica_functional.py +9 -9
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_stage_customizer.py +2 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_streaming.py +8 -8
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_streaming_nginx.py +2 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_streaming_replica.py +2 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_streaming_replica_nginx.py +2 -2
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_views.py +71 -34
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_views_push_external.py +24 -3
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_views_status.py +67 -14
- devpi-server-6.13.0/tox.ini +78 -0
- devpi-server-6.12.1/devpi_server/__init__.py +0 -1
- devpi-server-6.12.1/devpi_server/filestore.py +0 -335
- devpi-server-6.12.1/pyproject.toml +0 -26
- devpi-server-6.12.1/tox.ini +0 -40
- {devpi-server-6.12.1 → devpi-server-6.13.0}/AUTHORS +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/LICENSE +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/MANIFEST.in +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/README.rst +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/__main__.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/auth.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/auth_basic.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/auth_devpi.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/__init__.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/crontab.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/devpi.service.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/launchd-macos.txt.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/nginx-devpi-caching-http.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/nginx-devpi-caching-location.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/nginx-devpi-caching-proxy.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/nginx-devpi-caching-server.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/nginx-devpi.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/supervisor-devpi.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/supervisord.conf.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/cfg/windows-service.txt.template +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/exceptions.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/filestore_fs.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/interfaces.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/keyfs_sqlite.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/keyfs_sqlite_fs.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/keyfs_types.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/markers.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/middleware.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/readonly.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/vendor/__init__.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/vendor/_pip.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server/view_auth.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/SOURCES.txt +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/dependency_links.txt +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/entry_points.txt +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/not-zip-safe +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/requires.txt +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/devpi_server.egg-info/top_level.txt +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/pytest_devpi_server/__init__.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/setup.cfg +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/__init__.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/conftest.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/example.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/badindexname/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/badusername/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/basescycle/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/createdmodified/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/deletedbase/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/mirrordata/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/mirrordata/root/pypi/dddttt/0.1.dev1/dddttt-0.1.dev1.tar.gz +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/modifiedpypi/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/nocreatedmodified/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/normalization/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/normalization/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/normalization_merge/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello-pkg/hello.pkg-1.1.tar.gz +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/normalization_merge/root/dev/hello.pkg/hello.pkg-1.0.tar.gz +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/norootpypi/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/nouser/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/removedindexplugin/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/removedindexplugin/user/dev/pkg/pkg-1.0.tar.gz +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/0.9/hello-0.9.tar.gz +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/toxresult_naming_scheme/root/dev/hello/sha256=ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73/hello-0.9.tar.gz.toxresult0 +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/importexportdata/toxresult_upload_default/dataindex.json +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/reqmock.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/simpypi.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_auth.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_authcheck.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_fileutil.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_genconfig.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_log.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_mirror_no_project_list.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_nginx.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_readonly.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_reqmock.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_view_auth.py +0 -0
- {devpi-server-6.12.1 → devpi-server-6.13.0}/test_devpi_server/test_views_patch.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[flake8]
|
|
2
2
|
ignore = E501,E741,W503
|
|
3
3
|
per-file-ignores =
|
|
4
|
-
plugin.py:E127,E128,E225,E231
|
|
4
|
+
plugin.py:E127,E128,E225,E231
|
|
5
5
|
importexport.py:E127
|
|
6
6
|
model.py:E128,E225,E231
|
|
7
7
|
setup.py:E121,E126
|
|
8
8
|
test_importexport.py:E121
|
|
9
|
-
test*.py:
|
|
9
|
+
test*.py:E126,E127,E128,E225,E226,E231,E251
|
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
.. towncrier release notes start
|
|
4
4
|
|
|
5
|
+
6.13.0 (2024-09-19)
|
|
6
|
+
===================
|
|
7
|
+
|
|
8
|
+
Deprecations and Removals
|
|
9
|
+
-------------------------
|
|
10
|
+
|
|
11
|
+
- Remove/Deprecate "master" related terminology in favor of "primary".
|
|
12
|
+
Usage related changes are the switch to ``--primary-url`` instead of ``--master-url`` and ``--role=primary`` instead of ``--role=master``.
|
|
13
|
+
Using the old terms will now output warnings.
|
|
14
|
+
The ``+status`` API has additional fields and the ``role`` field content will change with 7.0.0.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
Features
|
|
19
|
+
--------
|
|
20
|
+
|
|
21
|
+
- Enable logging command line options for all commands.
|
|
22
|
+
|
|
23
|
+
- Added support uv pip as an installer.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Bug Fixes
|
|
28
|
+
---------
|
|
29
|
+
|
|
30
|
+
- Don't report on lagging event processing while replicating.
|
|
31
|
+
|
|
32
|
+
- Report primary serial correctly with streaming replication.
|
|
33
|
+
|
|
34
|
+
- Don't store file data in memory when fetching a release while pushing from a mirror.
|
|
35
|
+
|
|
36
|
+
- Only warn about replica not being in sync instead of fatal status while still replicating.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
5
40
|
6.12.1 (2024-07-24)
|
|
6
41
|
===================
|
|
7
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devpi-server
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.13.0
|
|
4
4
|
Summary: devpi-server: reliable private and pypi.org caching server
|
|
5
5
|
Home-page: https://devpi.net
|
|
6
6
|
Maintainer: Florian Schulze
|
|
@@ -9,6 +9,7 @@ License: MIT
|
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/devpi/devpi/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/devpi/devpi/blob/main/server/CHANGELOG
|
|
11
11
|
Project-URL: Documentation, https://doc.devpi.net
|
|
12
|
+
Project-URL: Funding, https://github.com/sponsors/devpi
|
|
12
13
|
Project-URL: Source Code, https://github.com/devpi/devpi
|
|
13
14
|
Keywords: pypi realtime cache server
|
|
14
15
|
Classifier: Development Status :: 5 - Production/Stable
|
|
@@ -104,6 +105,41 @@ Changelog
|
|
|
104
105
|
|
|
105
106
|
.. towncrier release notes start
|
|
106
107
|
|
|
108
|
+
6.13.0 (2024-09-19)
|
|
109
|
+
===================
|
|
110
|
+
|
|
111
|
+
Deprecations and Removals
|
|
112
|
+
-------------------------
|
|
113
|
+
|
|
114
|
+
- Remove/Deprecate "master" related terminology in favor of "primary".
|
|
115
|
+
Usage related changes are the switch to ``--primary-url`` instead of ``--master-url`` and ``--role=primary`` instead of ``--role=master``.
|
|
116
|
+
Using the old terms will now output warnings.
|
|
117
|
+
The ``+status`` API has additional fields and the ``role`` field content will change with 7.0.0.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
Features
|
|
122
|
+
--------
|
|
123
|
+
|
|
124
|
+
- Enable logging command line options for all commands.
|
|
125
|
+
|
|
126
|
+
- Added support uv pip as an installer.
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
Bug Fixes
|
|
131
|
+
---------
|
|
132
|
+
|
|
133
|
+
- Don't report on lagging event processing while replicating.
|
|
134
|
+
|
|
135
|
+
- Report primary serial correctly with streaming replication.
|
|
136
|
+
|
|
137
|
+
- Don't store file data in memory when fetching a release while pushing from a mirror.
|
|
138
|
+
|
|
139
|
+
- Only warn about replica not being in sync instead of fatal status while still replicating.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
107
143
|
6.12.1 (2024-07-24)
|
|
108
144
|
===================
|
|
109
145
|
|
|
@@ -178,12 +214,3 @@ Bug Fixes
|
|
|
178
214
|
- Only compare hostname instead of full URL prefix when parsing mirror packages to fix mirrors with basic authentication and absolute URLs. See #1006
|
|
179
215
|
|
|
180
216
|
|
|
181
|
-
|
|
182
|
-
6.9.2 (2023-08-06)
|
|
183
|
-
==================
|
|
184
|
-
|
|
185
|
-
Bug Fixes
|
|
186
|
-
---------
|
|
187
|
-
|
|
188
|
-
- Prevent duplicates when adding values to lists in index configuration with ``+=`` operator.
|
|
189
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '6.13.0'
|
|
@@ -6,6 +6,7 @@ import secrets
|
|
|
6
6
|
import sys
|
|
7
7
|
import uuid
|
|
8
8
|
from operator import itemgetter
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from tempfile import NamedTemporaryFile
|
|
10
11
|
from pluggy import HookimplMarker, PluginManager
|
|
11
12
|
import py
|
|
@@ -17,6 +18,8 @@ from . import hookspecs
|
|
|
17
18
|
import json
|
|
18
19
|
import devpi_server
|
|
19
20
|
from devpi_common.url import URL
|
|
21
|
+
import warnings
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
log = threadlog
|
|
22
25
|
|
|
@@ -70,17 +73,31 @@ def add_configfile_option(parser, pluginmanager):
|
|
|
70
73
|
def add_role_option(parser, pluginmanager):
|
|
71
74
|
parser.addoption(
|
|
72
75
|
"--role", action="store", dest="role", default="auto",
|
|
73
|
-
choices=["master", "replica", "standalone", "auto"],
|
|
76
|
+
choices=["master", "primary", "replica", "standalone", "auto"],
|
|
74
77
|
help="set role of this instance. The default 'auto' sets "
|
|
75
|
-
"'standalone' by default and 'replica' if the --
|
|
78
|
+
"'standalone' by default and 'replica' if the --primary-url "
|
|
76
79
|
"option is used. To enable the replication protocol you have "
|
|
77
|
-
"to explicitly set the 'master' role
|
|
80
|
+
"to explicitly set the 'primary' role. The 'master' role is "
|
|
81
|
+
"the deprecated variant of 'primary'.")
|
|
78
82
|
|
|
79
83
|
|
|
80
84
|
def add_master_url_option(parser, pluginmanager):
|
|
85
|
+
warnings.warn(
|
|
86
|
+
"The add_master_url_option function is deprecated, "
|
|
87
|
+
"use add_primary_url_option instead",
|
|
88
|
+
DeprecationWarning,
|
|
89
|
+
stacklevel=2)
|
|
90
|
+
add_primary_url_option(parser, pluginmanager)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def add_primary_url_option(parser, pluginmanager): # noqa: ARG001
|
|
81
94
|
parser.addoption(
|
|
82
|
-
"--
|
|
83
|
-
help="run as a replica of the specified
|
|
95
|
+
"--primary-url", action="store", dest="primary_url",
|
|
96
|
+
help="run as a replica of the specified primary server",
|
|
97
|
+
default=None)
|
|
98
|
+
parser.addoption(
|
|
99
|
+
"--master-url", action="store", dest="deprecated_master_url",
|
|
100
|
+
help="DEPRECATED, use --primary-url instead",
|
|
84
101
|
default=None)
|
|
85
102
|
|
|
86
103
|
|
|
@@ -186,7 +203,7 @@ def add_mirror_options(parser, pluginmanager):
|
|
|
186
203
|
|
|
187
204
|
|
|
188
205
|
def add_replica_options(parser, pluginmanager):
|
|
189
|
-
|
|
206
|
+
add_primary_url_option(parser, pluginmanager)
|
|
190
207
|
|
|
191
208
|
parser.addoption(
|
|
192
209
|
"--replica-max-retries", type=int, metavar="NUM",
|
|
@@ -197,7 +214,7 @@ def add_replica_options(parser, pluginmanager):
|
|
|
197
214
|
parser.addoption(
|
|
198
215
|
"--replica-file-search-path", metavar="PATH",
|
|
199
216
|
help="path to existing files to try before downloading "
|
|
200
|
-
"from
|
|
217
|
+
"from primary. These could be from a previous "
|
|
201
218
|
"replication attempt or downloaded separately. "
|
|
202
219
|
"Expects the structure from previous state or +files.")
|
|
203
220
|
|
|
@@ -214,13 +231,13 @@ def add_replica_options(parser, pluginmanager):
|
|
|
214
231
|
parser.addoption(
|
|
215
232
|
"--file-replication-threads", type=int, metavar="NUM",
|
|
216
233
|
default=DEFAULT_FILE_REPLICATION_THREADS,
|
|
217
|
-
help="number of threads for file download from
|
|
234
|
+
help="number of threads for file download from primary")
|
|
218
235
|
|
|
219
236
|
parser.addoption(
|
|
220
237
|
"--proxy-timeout", type=int, metavar="NUM",
|
|
221
238
|
default=DEFAULT_PROXY_TIMEOUT,
|
|
222
239
|
help="Number of seconds to wait before proxied requests from "
|
|
223
|
-
"the replica to the
|
|
240
|
+
"the replica to the primary time out (login, uploads etc).")
|
|
224
241
|
|
|
225
242
|
parser.addoption(
|
|
226
243
|
"--no-replica-streaming", dest="replica_streaming",
|
|
@@ -555,7 +572,7 @@ class MyArgumentParser(argparse.ArgumentParser):
|
|
|
555
572
|
default = action.default
|
|
556
573
|
if isinstance(action, argparse._StoreFalseAction):
|
|
557
574
|
default = not default
|
|
558
|
-
if action.help and
|
|
575
|
+
if action.help and argparse.SUPPRESS not in (action.help, default):
|
|
559
576
|
action.help += " [%s]" % default
|
|
560
577
|
|
|
561
578
|
def addgroup(self, *args, **kwargs):
|
|
@@ -584,8 +601,19 @@ class MyArgumentParser(argparse.ArgumentParser):
|
|
|
584
601
|
def add_init_options(self):
|
|
585
602
|
add_init_options(self, self.pluginmanager)
|
|
586
603
|
|
|
604
|
+
def add_logging_options(self) -> None:
|
|
605
|
+
add_logging_options(self, self.pluginmanager)
|
|
606
|
+
|
|
587
607
|
def add_master_url_option(self):
|
|
588
|
-
|
|
608
|
+
warnings.warn(
|
|
609
|
+
"The add_master_url_option method is deprecated, "
|
|
610
|
+
"use add_primary_url_option instead",
|
|
611
|
+
DeprecationWarning,
|
|
612
|
+
stacklevel=2)
|
|
613
|
+
add_primary_url_option(self, self.pluginmanager)
|
|
614
|
+
|
|
615
|
+
def add_primary_url_option(self):
|
|
616
|
+
add_primary_url_option(self, self.pluginmanager)
|
|
589
617
|
|
|
590
618
|
def add_role_option(self):
|
|
591
619
|
add_role_option(self, self.pluginmanager)
|
|
@@ -656,14 +684,32 @@ class Config(object):
|
|
|
656
684
|
|
|
657
685
|
@cached_property
|
|
658
686
|
def serverdir(self):
|
|
659
|
-
|
|
687
|
+
warnings.warn(
|
|
688
|
+
"The serverdir property is deprecated, "
|
|
689
|
+
"use server_path instead",
|
|
690
|
+
DeprecationWarning,
|
|
691
|
+
stacklevel=3)
|
|
692
|
+
return py.path.local(self.server_path)
|
|
693
|
+
|
|
694
|
+
@cached_property
|
|
695
|
+
def server_path(self):
|
|
696
|
+
return Path(self.args.serverdir).expanduser()
|
|
660
697
|
|
|
661
698
|
@cached_property
|
|
662
699
|
def path_nodeinfo(self):
|
|
663
|
-
|
|
700
|
+
warnings.warn(
|
|
701
|
+
"The path_nodeinfo property is deprecated, "
|
|
702
|
+
"use nodeinfo_path instead",
|
|
703
|
+
DeprecationWarning,
|
|
704
|
+
stacklevel=3)
|
|
705
|
+
return py.path.local(self.nodeinfo_path)
|
|
706
|
+
|
|
707
|
+
@cached_property
|
|
708
|
+
def nodeinfo_path(self):
|
|
709
|
+
return self.server_path / ".nodeinfo"
|
|
664
710
|
|
|
665
711
|
def init_nodeinfo(self):
|
|
666
|
-
log.info("Loading node info from %s", self.
|
|
712
|
+
log.info("Loading node info from %s", self.nodeinfo_path)
|
|
667
713
|
self._determine_role()
|
|
668
714
|
self._determine_uuid()
|
|
669
715
|
self._determine_storage()
|
|
@@ -677,56 +723,111 @@ class Config(object):
|
|
|
677
723
|
|
|
678
724
|
@property
|
|
679
725
|
def role(self):
|
|
680
|
-
|
|
726
|
+
role = self.nodeinfo["role"]
|
|
727
|
+
if role == "master":
|
|
728
|
+
warnings.warn(
|
|
729
|
+
"role==master is deprecated, use primary instead.",
|
|
730
|
+
DeprecationWarning,
|
|
731
|
+
stacklevel=2)
|
|
732
|
+
return "primary"
|
|
733
|
+
return role
|
|
681
734
|
|
|
682
735
|
def set_uuid(self, uuid):
|
|
683
736
|
# called when importing state
|
|
684
737
|
self.nodeinfo["uuid"] = uuid
|
|
685
738
|
self.write_nodeinfo()
|
|
686
739
|
|
|
687
|
-
def
|
|
688
|
-
assert self.role == "replica", "can only set
|
|
689
|
-
existing = self.
|
|
740
|
+
def set_primary_uuid(self, uuid):
|
|
741
|
+
assert self.role == "replica", "can only set primary uuid on replica"
|
|
742
|
+
existing = self.get_primary_uuid()
|
|
690
743
|
if existing and existing != uuid:
|
|
691
|
-
raise ValueError("already have
|
|
744
|
+
raise ValueError("already have primary id %r, got %r" % (
|
|
692
745
|
existing, uuid))
|
|
693
|
-
self.nodeinfo["
|
|
746
|
+
self.nodeinfo["primary-uuid"] = uuid
|
|
694
747
|
self.write_nodeinfo()
|
|
695
748
|
|
|
696
749
|
def get_master_uuid(self):
|
|
750
|
+
warnings.warn(
|
|
751
|
+
"get_master_uuid is deprecated, use get_primary_uuid instead",
|
|
752
|
+
DeprecationWarning,
|
|
753
|
+
stacklevel=2)
|
|
754
|
+
return self.get_primary_uuid()
|
|
755
|
+
|
|
756
|
+
def get_primary_uuid(self):
|
|
697
757
|
if self.role != "replica":
|
|
698
758
|
return self.nodeinfo["uuid"]
|
|
699
|
-
|
|
759
|
+
if "master-uuid" in self.nodeinfo:
|
|
760
|
+
warnings.warn(
|
|
761
|
+
"master-uuid in nodeinfo is deprecated, use primary-uuid instead",
|
|
762
|
+
DeprecationWarning,
|
|
763
|
+
stacklevel=2)
|
|
764
|
+
return self.nodeinfo["master-uuid"]
|
|
765
|
+
return self.nodeinfo.get("primary-uuid")
|
|
700
766
|
|
|
701
767
|
@cached_property
|
|
702
768
|
def nodeinfo(self):
|
|
703
|
-
if self.
|
|
704
|
-
|
|
769
|
+
if self.nodeinfo_path.is_file():
|
|
770
|
+
with self.nodeinfo_path.open() as f:
|
|
771
|
+
return json.load(f)
|
|
705
772
|
return {}
|
|
706
773
|
|
|
707
774
|
def write_nodeinfo(self):
|
|
708
|
-
nodeinfo_dir = self.
|
|
709
|
-
nodeinfo_dir.
|
|
710
|
-
prefix = "-" + self.
|
|
711
|
-
with NamedTemporaryFile(prefix=prefix, delete=False, dir=nodeinfo_dir
|
|
775
|
+
nodeinfo_dir = self.nodeinfo_path.parent
|
|
776
|
+
nodeinfo_dir.mkdir(parents=True, exist_ok=True)
|
|
777
|
+
prefix = "-" + self.nodeinfo_path.name
|
|
778
|
+
with NamedTemporaryFile(prefix=prefix, delete=False, dir=nodeinfo_dir) as f:
|
|
712
779
|
f.write(json.dumps(self.nodeinfo, indent=2).encode('utf-8'))
|
|
713
|
-
fileutil.rename(f.name, self.
|
|
714
|
-
threadlog.info("wrote nodeinfo to: %s", self.
|
|
780
|
+
fileutil.rename(f.name, self.nodeinfo_path)
|
|
781
|
+
threadlog.info("wrote nodeinfo to: %s", self.nodeinfo_path)
|
|
782
|
+
|
|
783
|
+
@property
|
|
784
|
+
def master_auth(self):
|
|
785
|
+
warnings.warn(
|
|
786
|
+
"master_auth is deprecated, use primary_auth instead",
|
|
787
|
+
DeprecationWarning,
|
|
788
|
+
stacklevel=2)
|
|
789
|
+
return self.primary_auth
|
|
715
790
|
|
|
716
791
|
@property
|
|
717
792
|
def master_url(self):
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
793
|
+
warnings.warn(
|
|
794
|
+
"master_url is deprecated, use primary_url instead",
|
|
795
|
+
DeprecationWarning,
|
|
796
|
+
stacklevel=2)
|
|
797
|
+
return self.primary_url
|
|
798
|
+
|
|
799
|
+
@property
|
|
800
|
+
def primary_auth(self):
|
|
801
|
+
# trigger setting of _primary_auth
|
|
802
|
+
return self._primary_auth if self.primary_url else None
|
|
803
|
+
|
|
804
|
+
@property
|
|
805
|
+
def primary_url(self):
|
|
806
|
+
if hasattr(self, '_primary_url'):
|
|
807
|
+
return self._primary_url
|
|
808
|
+
primary_url = None
|
|
809
|
+
if getattr(self.args, 'deprecated_master_url', None):
|
|
810
|
+
if getattr(self.args, 'primary_url', None):
|
|
811
|
+
from .main import fatal
|
|
812
|
+
fatal("Can't use both --master-url and --primary-url")
|
|
813
|
+
warnings.warn(
|
|
814
|
+
"The --master-url option is deprecated, "
|
|
815
|
+
"use --primary-url instead.",
|
|
816
|
+
DeprecationWarning,
|
|
817
|
+
stacklevel=2)
|
|
818
|
+
threadlog.warning(
|
|
819
|
+
"The --master-url option is deprecated, "
|
|
820
|
+
"use --primary-url instead.")
|
|
821
|
+
primary_url = URL(self.args.deprecated_master_url)
|
|
822
|
+
elif getattr(self.args, 'primary_url', None):
|
|
823
|
+
primary_url = URL(self.args.primary_url)
|
|
723
824
|
elif self.nodeinfo.get("masterurl"):
|
|
724
|
-
|
|
725
|
-
self.
|
|
726
|
-
return self.
|
|
825
|
+
primary_url = URL(self.nodeinfo["masterurl"])
|
|
826
|
+
self.primary_url = primary_url
|
|
827
|
+
return self.primary_url
|
|
727
828
|
|
|
728
|
-
@
|
|
729
|
-
def
|
|
829
|
+
@primary_url.setter
|
|
830
|
+
def primary_url(self, value):
|
|
730
831
|
auth = (None, None)
|
|
731
832
|
if value is not None:
|
|
732
833
|
auth = (value.username, value.password)
|
|
@@ -736,8 +837,8 @@ class Config(object):
|
|
|
736
837
|
value = value.replace(netloc=netloc)
|
|
737
838
|
if auth == (None, None):
|
|
738
839
|
auth = None
|
|
739
|
-
self.
|
|
740
|
-
self.
|
|
840
|
+
self._primary_auth = auth
|
|
841
|
+
self._primary_url = value
|
|
741
842
|
|
|
742
843
|
@property
|
|
743
844
|
def include_mirrored_files(self):
|
|
@@ -813,25 +914,25 @@ class Config(object):
|
|
|
813
914
|
return getattr(self.args, 'wait_for_events', False)
|
|
814
915
|
|
|
815
916
|
def _init_role(self):
|
|
816
|
-
if self.
|
|
917
|
+
if self.primary_url:
|
|
817
918
|
self.nodeinfo["role"] = "replica"
|
|
818
919
|
else:
|
|
819
920
|
self.nodeinfo["role"] = "standalone"
|
|
820
921
|
|
|
821
922
|
def _automatic_role(self, role):
|
|
822
923
|
from .main import Fatal
|
|
823
|
-
if role == "replica" and not self.
|
|
924
|
+
if role == "replica" and not self.primary_url:
|
|
824
925
|
raise Fatal(
|
|
825
|
-
"configuration error,
|
|
926
|
+
"configuration error, primary URL isn't set in nodeinfo, but "
|
|
826
927
|
"role is set to replica")
|
|
827
|
-
if role != "replica" and self.
|
|
928
|
+
if role != "replica" and self.primary_url:
|
|
828
929
|
raise Fatal(
|
|
829
|
-
"configuration error,
|
|
930
|
+
"configuration error, primary URL set in nodeinfo, but role "
|
|
830
931
|
"isn't set to replica")
|
|
831
932
|
if role != "replica":
|
|
832
|
-
self.
|
|
833
|
-
if role
|
|
834
|
-
# we only allow explicit
|
|
933
|
+
self.primary_url = None
|
|
934
|
+
if role in ("master", "primary"):
|
|
935
|
+
# we only allow explicit primary role
|
|
835
936
|
self.nodeinfo["role"] = "standalone"
|
|
836
937
|
|
|
837
938
|
def _change_role(self, old_role, new_role):
|
|
@@ -840,10 +941,10 @@ class Config(object):
|
|
|
840
941
|
if old_role and old_role != "replica":
|
|
841
942
|
msg = f"cannot run as replica, was previously run as {old_role}"
|
|
842
943
|
raise Fatal(msg)
|
|
843
|
-
if not self.
|
|
844
|
-
raise Fatal("need to specify --
|
|
944
|
+
if not self.primary_url:
|
|
945
|
+
raise Fatal("need to specify --primary-url to run as replica")
|
|
845
946
|
else:
|
|
846
|
-
self.
|
|
947
|
+
self.primary_url = None
|
|
847
948
|
self.nodeinfo["role"] = new_role
|
|
848
949
|
|
|
849
950
|
def _determine_role(self):
|
|
@@ -857,9 +958,9 @@ class Config(object):
|
|
|
857
958
|
self._change_role(old_role, role)
|
|
858
959
|
assert self.nodeinfo["role"]
|
|
859
960
|
if self.nodeinfo["role"] == "replica":
|
|
860
|
-
assert self.
|
|
861
|
-
if self.
|
|
862
|
-
self.nodeinfo["masterurl"] = self.
|
|
961
|
+
assert self.primary_url
|
|
962
|
+
if self.primary_url:
|
|
963
|
+
self.nodeinfo["masterurl"] = self.primary_url.url
|
|
863
964
|
else:
|
|
864
965
|
self.nodeinfo.pop("masterurl", None)
|
|
865
966
|
|
|
@@ -901,36 +1002,45 @@ class Config(object):
|
|
|
901
1002
|
def sqlite_file_needed_but_missing(self):
|
|
902
1003
|
return (
|
|
903
1004
|
self.storage_info['name'] == 'sqlite'
|
|
904
|
-
and not self.
|
|
1005
|
+
and not self.server_path.joinpath(".sqlite").exists()
|
|
905
1006
|
)
|
|
906
1007
|
|
|
907
1008
|
@cached_property
|
|
908
1009
|
def secretfile(self):
|
|
1010
|
+
warnings.warn(
|
|
1011
|
+
"The secretfile property is deprecated, "
|
|
1012
|
+
"use secret_path instead",
|
|
1013
|
+
DeprecationWarning,
|
|
1014
|
+
stacklevel=3)
|
|
1015
|
+
return py.path.local(self.secret_path)
|
|
1016
|
+
|
|
1017
|
+
@cached_property
|
|
1018
|
+
def secret_path(self):
|
|
909
1019
|
if not self.args.secretfile:
|
|
910
|
-
secretfile = self.
|
|
911
|
-
if not secretfile.
|
|
1020
|
+
secretfile = self.server_path / '.secret'
|
|
1021
|
+
if not secretfile.is_file():
|
|
912
1022
|
return None
|
|
913
1023
|
log.warning(
|
|
914
1024
|
"Using deprecated existing secret file at '%s', use "
|
|
915
1025
|
"--secretfile to explicitly provide the location." % secretfile)
|
|
916
1026
|
return secretfile
|
|
917
|
-
return
|
|
918
|
-
os.path.expanduser(self.args.secretfile))
|
|
1027
|
+
return Path(self.args.secretfile).expanduser()
|
|
919
1028
|
|
|
920
1029
|
def get_validated_secret(self):
|
|
921
1030
|
from .main import Fatal
|
|
922
1031
|
import stat
|
|
923
|
-
|
|
1032
|
+
secret_path = self.secret_path
|
|
1033
|
+
if not secret_path.is_file():
|
|
924
1034
|
raise Fatal("The given secret file doesn't exist.")
|
|
925
|
-
if
|
|
1035
|
+
if secret_path.stat().st_mode & stat.S_IRWXO and sys.platform != "win32":
|
|
926
1036
|
raise Fatal("The given secret file is world accessible, the access mode must be user accessible only (0600).")
|
|
927
|
-
if
|
|
1037
|
+
if secret_path.stat().st_mode & stat.S_IRWXG and sys.platform != "win32":
|
|
928
1038
|
raise Fatal("The given secret file is group accessible, the access mode must be user accessible only (0600).")
|
|
929
|
-
if
|
|
1039
|
+
if secret_path.parent.stat().st_mode & stat.S_IWGRP and sys.platform != "win32":
|
|
930
1040
|
raise Fatal("The folder of the given secret file is group writable, it must only be writable by the user.")
|
|
931
|
-
if
|
|
1041
|
+
if secret_path.parent.stat().st_mode & stat.S_IWOTH and sys.platform != "win32":
|
|
932
1042
|
raise Fatal("The folder of the given secret file is world writable, it must only be writable by the user.")
|
|
933
|
-
secret =
|
|
1043
|
+
secret = secret_path.read_bytes()
|
|
934
1044
|
if len(secret) < 32:
|
|
935
1045
|
raise Fatal(
|
|
936
1046
|
"The secret in the given secret file is too short, "
|
|
@@ -943,7 +1053,7 @@ class Config(object):
|
|
|
943
1053
|
|
|
944
1054
|
@cached_property
|
|
945
1055
|
def basesecret(self):
|
|
946
|
-
if self.
|
|
1056
|
+
if self.secret_path is None:
|
|
947
1057
|
log.warning(
|
|
948
1058
|
"No secret file provided, creating a new random secret. "
|
|
949
1059
|
"Login tokens issued before are invalid. "
|
|
@@ -952,7 +1062,7 @@ class Config(object):
|
|
|
952
1062
|
"devpi-gen-secret command.")
|
|
953
1063
|
return new_secret()
|
|
954
1064
|
secret = self.get_validated_secret()
|
|
955
|
-
log.info("Using secret file '%s'.", self.
|
|
1065
|
+
log.info("Using secret file '%s'.", self.secret_path)
|
|
956
1066
|
return secret
|
|
957
1067
|
|
|
958
1068
|
@property
|
|
@@ -994,7 +1104,6 @@ def getpath(path):
|
|
|
994
1104
|
|
|
995
1105
|
|
|
996
1106
|
def gensecret():
|
|
997
|
-
from .log import configure_cli_logging
|
|
998
1107
|
from .log import threadlog as log
|
|
999
1108
|
from .main import CommandRunner
|
|
1000
1109
|
from .main import Fatal
|
|
@@ -1005,22 +1114,22 @@ def gensecret():
|
|
|
1005
1114
|
add_help=False)
|
|
1006
1115
|
parser.add_help_option()
|
|
1007
1116
|
parser.add_configfile_option()
|
|
1117
|
+
parser.add_logging_options()
|
|
1008
1118
|
parser.add_secretfile_option()
|
|
1009
1119
|
config = runner.get_config(sys.argv, parser=parser)
|
|
1010
|
-
|
|
1120
|
+
runner.configure_logging(config.args)
|
|
1011
1121
|
if config.args.secretfile is None:
|
|
1012
1122
|
raise Fatal("You need to provide a location for the secret file.")
|
|
1013
|
-
if not config.
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
log.info("New secret written to '%s'" % config.secretfile)
|
|
1123
|
+
if not config.secret_path.exists():
|
|
1124
|
+
config.secret_path.write_bytes(new_secret())
|
|
1125
|
+
log.info("New secret written to '%s'" % config.secret_path)
|
|
1017
1126
|
if sys.platform != "win32":
|
|
1018
|
-
mode = config.
|
|
1127
|
+
mode = config.secret_path.stat().st_mode
|
|
1019
1128
|
if mode & stat.S_IRWXG or mode & stat.S_IRWXO:
|
|
1020
|
-
config.
|
|
1129
|
+
config.secret_path.chmod(0o600)
|
|
1021
1130
|
log.info("Changed file mode to 0600 to adjust access permissions.")
|
|
1022
1131
|
else:
|
|
1023
|
-
log.info("Checking existing secret at '%s'" % config.
|
|
1132
|
+
log.info("Checking existing secret at '%s'" % config.secret_path)
|
|
1024
1133
|
# run checks
|
|
1025
1134
|
config.get_validated_secret()
|
|
1026
1135
|
log.info("Permissions of secret file look good.")
|