pyreposync 0.1.0__tar.gz → 0.2.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.
- {pyreposync-0.1.0 → pyreposync-0.2.0}/.gitignore +3 -0
- {pyreposync-0.1.0 → pyreposync-0.2.0}/PKG-INFO +1 -1
- pyreposync-0.2.0/contrib/reposync_2.ini +15 -0
- {pyreposync-0.1.0 → pyreposync-0.2.0}/pyproject.toml +1 -1
- {pyreposync-0.1.0 → pyreposync-0.2.0}/pyreposync/__init__.py +143 -156
- {pyreposync-0.1.0 → pyreposync-0.2.0}/pyreposync/downloader.py +49 -15
- pyreposync-0.2.0/pyreposync/sync_deb.py +218 -0
- pyreposync-0.2.0/pyreposync/sync_generic.py +169 -0
- pyreposync-0.2.0/pyreposync/sync_rpm.py +256 -0
- pyreposync-0.2.0/reposync.ini +19 -0
- pyreposync-0.1.0/contrib/reposync.ini +0 -422
- pyreposync-0.1.0/pyreposync/rpm_sync.py +0 -408
- {pyreposync-0.1.0 → pyreposync-0.2.0}/LICENSE.txt +0 -0
- {pyreposync-0.1.0 → pyreposync-0.2.0}/README.rst +0 -0
- {pyreposync-0.1.0 → pyreposync-0.2.0}/pyreposync/exceptions.py +0 -0
- {pyreposync-0.1.0 → pyreposync-0.2.0}/requirements.txt +0 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
[main]
|
2
|
+
destination = /home/schlitzer/PycharmProjects/pyreposync/_data
|
3
|
+
downloaders = 10
|
4
|
+
log = /home/schlitzer/PycharmProjects/pyreposync/_data/pyreposync.log
|
5
|
+
logretention = 7
|
6
|
+
loglevel = INFO
|
7
|
+
#proxy = http://proxy.example.com:3128
|
8
|
+
|
9
|
+
[AlmaLinux/9/AppStream/x86_64:rpm]
|
10
|
+
baseurl = https://mirror.23m.com/almalinux/9/AppStream/x86_64/os/
|
11
|
+
tags = EXTERNAL,OS:AlmaLinux9,BASE,X86_64
|
12
|
+
|
13
|
+
[AlmaLinux/9/BaseOS/x86_64:rpm]
|
14
|
+
baseurl = https://mirror.23m.com/almalinux/9/BaseOS/x86_64/os/
|
15
|
+
tags = EXTERNAL,OS:AlmaLinux9,BASE,X86_64
|
@@ -9,7 +9,8 @@ import time
|
|
9
9
|
|
10
10
|
from pyreposync.downloader import Downloader
|
11
11
|
from pyreposync.exceptions import OSRepoSyncException, OSRepoSyncHashError
|
12
|
-
from pyreposync.
|
12
|
+
from pyreposync.sync_rpm import SyncRPM
|
13
|
+
from pyreposync.sync_deb import SyncDeb
|
13
14
|
|
14
15
|
|
15
16
|
def main():
|
@@ -56,6 +57,14 @@ def main():
|
|
56
57
|
)
|
57
58
|
subparsers.required = True
|
58
59
|
|
60
|
+
migrate_parser = subparsers.add_parser(
|
61
|
+
"migrate",
|
62
|
+
help="migrate to new sync format",
|
63
|
+
)
|
64
|
+
migrate_parser.set_defaults(
|
65
|
+
method="migrate",
|
66
|
+
)
|
67
|
+
|
59
68
|
snap_cleanup_parser = subparsers.add_parser(
|
60
69
|
"snap_cleanup",
|
61
70
|
help="remove all unnamed snapshots and unreferenced rpms.",
|
@@ -248,34 +257,13 @@ class PyRepoSync:
|
|
248
257
|
def config_dict(self):
|
249
258
|
return self._config_dict
|
250
259
|
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
for version in versions.split(","):
|
256
|
-
job = RPMSync(
|
257
|
-
base_url=self.config.get(section, "baseurl").replace(
|
258
|
-
":VERSION:", version
|
259
|
-
),
|
260
|
-
destination=self.config.get("main", "destination"),
|
261
|
-
reponame=section[:-4].replace(":VERSION:", version),
|
262
|
-
syncdir=self.config.get(section, "syncdir", fallback=None),
|
263
|
-
date=date,
|
264
|
-
treeinfo=self.config.get(section, "treeinfo", fallback=".treeinfo"),
|
265
|
-
proxy=self.config.get("main", "proxy", fallback=None),
|
266
|
-
client_cert=self.config.get(
|
267
|
-
section, "sslclientcert", fallback=None
|
268
|
-
),
|
269
|
-
client_key=self.config.get(section, "sslclientkey", fallback=None),
|
270
|
-
ca_cert=self.config.get(section, "sslcacert", fallback=None),
|
271
|
-
)
|
272
|
-
jobs.add(job)
|
273
|
-
else:
|
274
|
-
job = RPMSync(
|
260
|
+
def get_job(self, date, section):
|
261
|
+
self.log.info(f"section name: {section}")
|
262
|
+
if section.endswith(":rpm"):
|
263
|
+
return SyncRPM(
|
275
264
|
base_url=self.config.get(section, "baseurl"),
|
276
265
|
destination=self.config.get("main", "destination"),
|
277
266
|
reponame=section[:-4],
|
278
|
-
syncdir=self.config.get(section, "syncdir", fallback=None),
|
279
267
|
date=date,
|
280
268
|
treeinfo=self.config.get(section, "treeinfo", fallback=".treeinfo"),
|
281
269
|
proxy=self.config.get("main", "proxy", fallback=None),
|
@@ -283,19 +271,32 @@ class PyRepoSync:
|
|
283
271
|
client_key=self.config.get(section, "sslclientkey", fallback=None),
|
284
272
|
ca_cert=self.config.get(section, "sslcacert", fallback=None),
|
285
273
|
)
|
286
|
-
|
287
|
-
|
274
|
+
elif section.endswith(":deb"):
|
275
|
+
return SyncDeb(
|
276
|
+
base_url=self.config.get(section, "baseurl"),
|
277
|
+
destination=self.config.get("main", "destination"),
|
278
|
+
reponame=section[:-4],
|
279
|
+
date=date,
|
280
|
+
proxy=self.config.get(section, "proxy", fallback=None),
|
281
|
+
client_cert=self.config.get(section, "sslclientcert", fallback=None),
|
282
|
+
client_key=self.config.get(section, "sslclientkey", fallback=None),
|
283
|
+
ca_cert=self.config.get(section, "sslcacert", fallback=None),
|
284
|
+
suites=self.config.get(section, "suites").split(),
|
285
|
+
components=self.config.get(section, "components").split(),
|
286
|
+
binary_archs=self.config.get(section, "binary_archs").split(),
|
287
|
+
)
|
288
288
|
|
289
289
|
def get_sections(self):
|
290
290
|
sections = set()
|
291
291
|
for section in self.config:
|
292
|
-
if section.endswith(":rpm"):
|
292
|
+
if section.endswith(":rpm") or section.endswith(":deb"):
|
293
293
|
if self.repo and section != self.repo:
|
294
294
|
continue
|
295
295
|
if self._tags:
|
296
296
|
if not self.validate_tags(section):
|
297
297
|
continue
|
298
298
|
sections.add(section)
|
299
|
+
|
299
300
|
return sections
|
300
301
|
|
301
302
|
def work(self):
|
@@ -303,7 +304,7 @@ class PyRepoSync:
|
|
303
304
|
date = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S")
|
304
305
|
queue = collections.deque()
|
305
306
|
for section in self.get_sections():
|
306
|
-
queue.append(self.
|
307
|
+
queue.append(self.get_job(date=date, section=section))
|
307
308
|
workers = set()
|
308
309
|
if self.method == "sync":
|
309
310
|
num_worker = self.config.getint("main", "downloaders", fallback=1)
|
@@ -324,7 +325,7 @@ class PyRepoSync:
|
|
324
325
|
return_code = 0
|
325
326
|
for worker in workers:
|
326
327
|
worker.join()
|
327
|
-
if worker.status
|
328
|
+
if worker.status != 0:
|
328
329
|
return_code = 1
|
329
330
|
sys.exit(return_code)
|
330
331
|
|
@@ -340,7 +341,7 @@ class PyRepoSync:
|
|
340
341
|
else:
|
341
342
|
if tag not in section_tags:
|
342
343
|
return False
|
343
|
-
self.log.info("section {
|
344
|
+
self.log.info(f"section {section} has matching tags")
|
344
345
|
return True
|
345
346
|
|
346
347
|
|
@@ -379,146 +380,132 @@ class RepoSyncThread(threading.Thread):
|
|
379
380
|
def timestamp(self):
|
380
381
|
return self._timestamp
|
381
382
|
|
382
|
-
def
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
self.log.info(
|
430
|
-
"{0} -> {1}".format(
|
431
|
-
timestamp, referenced_timestamps.get(timestamp, [])
|
432
|
-
)
|
433
|
-
)
|
434
|
-
self.log.info("The following named snapshots exist:")
|
435
|
-
base = "{0}/snap/{1}/".format(job.destination, job.reponame)
|
436
|
-
for named in job.snap_list_named_snapshots():
|
437
|
-
named = "named/{0}".format(named)
|
438
|
-
self.log.info(
|
439
|
-
"{0} -> {1}".format(
|
440
|
-
named,
|
441
|
-
job.snap_list_named_snapshot_target(
|
442
|
-
"{0}/{1}".format(base, named)
|
443
|
-
),
|
444
|
-
)
|
445
|
-
)
|
446
|
-
latest = "{0}/latest".format(base)
|
383
|
+
def do_migrate(self, job):
|
384
|
+
try:
|
385
|
+
self.name = job.reponame
|
386
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
387
|
+
job.migrate()
|
388
|
+
self.log.info(f"{self.action} done repo {job.reponame}")
|
389
|
+
except OSRepoSyncException:
|
390
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
391
|
+
self.status = 1
|
392
|
+
|
393
|
+
def do_sync(self, job):
|
394
|
+
try:
|
395
|
+
self.name = job.reponame
|
396
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
397
|
+
job.sync()
|
398
|
+
self.log.info(f"{self.action} done repo {job.reponame}")
|
399
|
+
except OSRepoSyncException:
|
400
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
401
|
+
self.status = 1
|
402
|
+
|
403
|
+
def do_snap(self, job):
|
404
|
+
try:
|
405
|
+
self.name = job.reponame
|
406
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
407
|
+
job.snap()
|
408
|
+
self.log.info(f"{self.action} done repo {job.reponame}")
|
409
|
+
except OSRepoSyncException:
|
410
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
411
|
+
self.status = 1
|
412
|
+
|
413
|
+
def do_snap_cleanup(self, job):
|
414
|
+
try:
|
415
|
+
self.name = job.reponame
|
416
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
417
|
+
job.snap_cleanup()
|
418
|
+
self.log.info(f"{self.action} done repo {job.reponame}")
|
419
|
+
except OSRepoSyncException:
|
420
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
421
|
+
self.status = 1
|
422
|
+
|
423
|
+
def do_snap_list(self, job):
|
424
|
+
try:
|
425
|
+
self.name = job.reponame
|
426
|
+
referenced_timestamps = job.snap_list_get_referenced_timestamps()
|
427
|
+
self.log.info(f"Repository: {job.reponame}")
|
428
|
+
self.log.info("The following timestamp snapshots exist:")
|
429
|
+
for timestamp in job.snap_list_timestamp_snapshots():
|
447
430
|
self.log.info(
|
448
|
-
"
|
449
|
-
)
|
450
|
-
|
451
|
-
except OSRepoSyncException:
|
452
|
-
self.status = 1
|
453
|
-
|
454
|
-
def do_snap_name(self, jobs):
|
455
|
-
for job in jobs:
|
456
|
-
try:
|
457
|
-
self.log.info("{0} start repo {1}".format(self.action, job.reponame))
|
458
|
-
self.name = job.reponame
|
459
|
-
job.snap_name(self.timestamp, self.snapname)
|
460
|
-
self.log.info("{0} done repo {1}".format(self.action, job.reponame))
|
461
|
-
except OSRepoSyncException:
|
462
|
-
self.log.fatal(
|
463
|
-
"could not {0} repo {1}".format(self.action, job.reponame)
|
431
|
+
f"{timestamp} -> {referenced_timestamps.get(timestamp, [])}"
|
464
432
|
)
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
433
|
+
self.log.info("The following named snapshots exist:")
|
434
|
+
base = f"{job.destination}/snap/{job.reponame}/"
|
435
|
+
for named in job.snap_list_named_snapshots():
|
436
|
+
timestamp = job.snap_list_named_snapshot_target(f"{base}/named/{named}")
|
437
|
+
self.log.info(f"named/{named} -> {timestamp}")
|
438
|
+
latest = f"{base}/latest"
|
439
|
+
self.log.info(f"latest -> {job.snap_list_named_snapshot_target(latest)}")
|
440
|
+
|
441
|
+
except OSRepoSyncException:
|
442
|
+
self.status = 1
|
443
|
+
|
444
|
+
def do_snap_name(self, job):
|
445
|
+
try:
|
446
|
+
self.name = job.reponame
|
447
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
448
|
+
job.snap_name(self.timestamp, self.snapname)
|
449
|
+
self.log.info(f"{self.action} done repo {job.reponame}")
|
450
|
+
except OSRepoSyncException:
|
451
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
452
|
+
self.status = 1
|
453
|
+
|
454
|
+
def do_snap_unname(self, job):
|
455
|
+
try:
|
456
|
+
self.name = job.reponame
|
457
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
458
|
+
job.snap_unname(self.snapname)
|
459
|
+
self.log.info(f"{self.action} done repo {job.reponame}")
|
460
|
+
except OSRepoSyncException:
|
461
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
462
|
+
self.status = 1
|
463
|
+
|
464
|
+
def do_validate(self, job):
|
481
465
|
_downloader = Downloader()
|
482
466
|
packages = dict()
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
"could not {0} repo {1}".format(self.action, job.reponame)
|
490
|
-
)
|
491
|
-
self.status = 1
|
467
|
+
try:
|
468
|
+
self.log.info(f"{self.action} start repo {job.reponame}")
|
469
|
+
packages.update(job.revalidate())
|
470
|
+
except OSRepoSyncException:
|
471
|
+
self.log.fatal(f"could not {self.action} repo {job.reponame}")
|
472
|
+
self.status = 1
|
492
473
|
for destination, hash_info in packages.items():
|
493
474
|
try:
|
494
|
-
self.log.info("validating: {
|
475
|
+
self.log.info(f"validating: {destination}")
|
495
476
|
_downloader.check_hash(
|
496
477
|
destination=destination,
|
497
478
|
checksum=hash_info["hash_sum"],
|
498
479
|
hash_type=hash_info["hash_algo"],
|
499
480
|
)
|
500
481
|
except OSRepoSyncHashError:
|
501
|
-
self.log.error("hash mismatch for: {
|
482
|
+
self.log.error(f"hash mismatch for: {destination}")
|
502
483
|
except FileNotFoundError:
|
503
|
-
self.log.error("file not found: {
|
484
|
+
self.log.error(f"file not found: {destination}")
|
504
485
|
|
505
486
|
def run(self):
|
506
487
|
while True:
|
507
488
|
try:
|
508
|
-
|
509
|
-
if self.action
|
510
|
-
self.
|
511
|
-
elif self.action
|
512
|
-
self.
|
513
|
-
elif self.action
|
514
|
-
self.
|
515
|
-
elif self.action
|
516
|
-
self.
|
517
|
-
elif self.action
|
518
|
-
self.
|
519
|
-
elif self.action
|
520
|
-
self.
|
521
|
-
elif self.action
|
522
|
-
self.
|
489
|
+
job = self.queue.pop()
|
490
|
+
if self.action == "migrate":
|
491
|
+
self.do_migrate(job)
|
492
|
+
elif self.action == "sync":
|
493
|
+
self.do_sync(job)
|
494
|
+
elif self.action == "snap_cleanup":
|
495
|
+
self.do_snap_cleanup(job)
|
496
|
+
elif self.action == "snap_list":
|
497
|
+
self.do_snap_list(job)
|
498
|
+
elif self.action == "snap_name":
|
499
|
+
self.do_snap_name(job)
|
500
|
+
elif self.action == "snap_unname":
|
501
|
+
self.do_snap_unname(job)
|
502
|
+
elif self.action == "snap":
|
503
|
+
self.do_snap(job)
|
504
|
+
elif self.action == "validate":
|
505
|
+
self.do_validate(job)
|
523
506
|
except IndexError:
|
524
507
|
break
|
508
|
+
|
509
|
+
|
510
|
+
if __name__ == "__main__":
|
511
|
+
main()
|
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
import requests
|
5
5
|
import requests.exceptions
|
6
6
|
import shutil
|
7
|
+
import tempfile
|
7
8
|
import time
|
8
9
|
|
9
10
|
from pyreposync.exceptions import OSRepoSyncDownLoadError, OSRepoSyncHashError
|
@@ -53,16 +54,24 @@ class Downloader(object):
|
|
53
54
|
|
54
55
|
with open(destination, "rb") as dest:
|
55
56
|
hasher.update(dest.read())
|
56
|
-
self.log.debug("expected hash: {
|
57
|
-
self.log.debug("actual hash: {
|
57
|
+
self.log.debug(f"expected hash: {hasher.hexdigest()}")
|
58
|
+
self.log.debug(f"actual hash: {checksum}")
|
58
59
|
if hasher.hexdigest() == checksum:
|
59
|
-
self.log.debug("download valid: {
|
60
|
+
self.log.debug(f"download valid: {destination}")
|
60
61
|
else:
|
61
|
-
self.log.error("download invalid: {
|
62
|
-
raise OSRepoSyncHashError("download invalid: {
|
62
|
+
self.log.error(f"download invalid: {destination}")
|
63
|
+
raise OSRepoSyncHashError(f"download invalid: {destination}")
|
63
64
|
|
64
|
-
def get(
|
65
|
-
self
|
65
|
+
def get(
|
66
|
+
self,
|
67
|
+
url,
|
68
|
+
destination,
|
69
|
+
checksum=None,
|
70
|
+
hash_type=None,
|
71
|
+
replace=False,
|
72
|
+
not_found_ok=False,
|
73
|
+
):
|
74
|
+
self.log.info(f"downloading: {url}")
|
66
75
|
if not replace:
|
67
76
|
if os.path.isfile(destination):
|
68
77
|
self.log.info("already there, not downloading")
|
@@ -70,8 +79,18 @@ class Downloader(object):
|
|
70
79
|
retries = 10
|
71
80
|
while retries >= 0:
|
72
81
|
try:
|
73
|
-
|
74
|
-
|
82
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
83
|
+
tmp_file = os.path.join(tmp_dir, os.path.basename(destination))
|
84
|
+
self._get(url, tmp_file, checksum, hash_type, not_found_ok)
|
85
|
+
self.create_dir(destination)
|
86
|
+
try:
|
87
|
+
shutil.move(tmp_file, destination)
|
88
|
+
except OSError:
|
89
|
+
if not_found_ok:
|
90
|
+
pass
|
91
|
+
else:
|
92
|
+
raise
|
93
|
+
self.log.info(f"done downloading: {url}")
|
75
94
|
return
|
76
95
|
except requests.exceptions.ConnectionError:
|
77
96
|
self.log.error("could not fetch resource, retry in 10 seconds")
|
@@ -83,15 +102,26 @@ class Downloader(object):
|
|
83
102
|
time.sleep(10)
|
84
103
|
except OSRepoSyncDownLoadError:
|
85
104
|
break
|
86
|
-
self.log.error("could not download: {
|
87
|
-
raise OSRepoSyncDownLoadError("could not download: {
|
105
|
+
self.log.error(f"could not download: {url}")
|
106
|
+
raise OSRepoSyncDownLoadError(f"could not download: {url}")
|
88
107
|
|
89
|
-
def
|
108
|
+
def create_dir(self, destination):
|
90
109
|
if not os.path.isdir(os.path.dirname(destination)):
|
91
110
|
try:
|
92
111
|
os.makedirs(os.path.dirname(destination))
|
93
|
-
except OSError:
|
94
|
-
|
112
|
+
except OSError as err:
|
113
|
+
self.log.error(f"could not create directory: {err}")
|
114
|
+
raise OSRepoSyncDownLoadError(f"could not create directory: {err}")
|
115
|
+
|
116
|
+
def _get(
|
117
|
+
self,
|
118
|
+
url,
|
119
|
+
destination,
|
120
|
+
checksum=None,
|
121
|
+
hash_type=None,
|
122
|
+
not_found_ok=False,
|
123
|
+
):
|
124
|
+
self.create_dir(destination)
|
95
125
|
r = requests.get(
|
96
126
|
url, stream=True, proxies=self.proxy, cert=self.cert, verify=self.ca_cert
|
97
127
|
)
|
@@ -101,9 +131,13 @@ class Downloader(object):
|
|
101
131
|
shutil.copyfileobj(r.raw, dst)
|
102
132
|
dst.flush()
|
103
133
|
else:
|
134
|
+
if not_found_ok:
|
135
|
+
if r.status_code == 404:
|
136
|
+
self.log.info("not found, skipping")
|
137
|
+
return
|
104
138
|
raise OSRepoSyncDownLoadError()
|
105
139
|
if checksum:
|
106
140
|
self.check_hash(
|
107
141
|
destination=destination, checksum=checksum, hash_type=hash_type
|
108
142
|
)
|
109
|
-
self.log.info("successfully fetched: {
|
143
|
+
self.log.info(f"successfully fetched: {url}")
|