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.
@@ -1,6 +1,9 @@
1
1
  .idea
2
2
  *__pycache__*
3
3
  pyreposync.egg-info
4
+ .eggs
5
+ _data
6
+ deb_mirror
4
7
  dist
5
8
  venv
6
9
  *xml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyreposync
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Orbit Api
5
5
  Project-URL: Source, https://github.com/schlitzered/pyreposync
6
6
  Author-email: "Stephan.Schultchen" <sschultchen@gmail.com>
@@ -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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyreposync"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  requires-python = ">=3.9"
9
9
  authors = [
10
10
  {name = "Stephan.Schultchen", email = "sschultchen@gmail.com"},
@@ -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.rpm_sync import RPMSync
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 get_jobs(self, date, section):
252
- versions = self.config.get(section, "versions", fallback=None)
253
- jobs = set()
254
- if versions:
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
- jobs.add(job)
287
- return jobs
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.get_jobs(date=date, section=section))
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 is not 0:
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 {0} has matching tags".format(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 do_sync(self, jobs):
383
- for job in jobs:
384
- try:
385
- self.log.info("{0} start repo {1}".format(self.action, job.reponame))
386
- self.name = job.reponame
387
- job.sync()
388
- self.log.info("{0} done repo {1}".format(self.action, job.reponame))
389
- except OSRepoSyncException:
390
- self.log.fatal(
391
- "could not {0} repo {1}".format(self.action, job.reponame)
392
- )
393
- self.status = 1
394
-
395
- def do_snap(self, jobs):
396
- for job in jobs:
397
- try:
398
- self.log.info("{0} start repo {1}".format(self.action, job.reponame))
399
- self.name = job.reponame
400
- job.snap()
401
- self.log.info("{0} done repo {1}".format(self.action, job.reponame))
402
- except OSRepoSyncException:
403
- self.log.fatal(
404
- "could not {0} repo {1}".format(self.action, job.reponame)
405
- )
406
- self.status = 1
407
-
408
- def do_snap_cleanup(self, jobs):
409
- for job in jobs:
410
- try:
411
- self.log.info("{0} start repo {1}".format(self.action, job.reponame))
412
- self.name = job.reponame
413
- job.snap_cleanup()
414
- self.log.info("{0} done repo {1}".format(self.action, job.reponame))
415
- except OSRepoSyncException:
416
- self.log.fatal(
417
- "could not {0} repo {1}".format(self.action, job.reponame)
418
- )
419
- self.status = 1
420
-
421
- def do_snap_list(self, jobs):
422
- for job in jobs:
423
- try:
424
- self.name = job.reponame
425
- referenced_timestamps = job.snap_list_get_referenced_timestamps()
426
- self.log.info("Repository: {0}".format(job.reponame))
427
- self.log.info("The following timestamp snapshots exist:")
428
- for timestamp in job.snap_list_timestamp_snapshots():
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
- "latest -> {0}".format(job.snap_list_named_snapshot_target(latest))
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
- self.status = 1
466
-
467
- def do_snap_unname(self, jobs):
468
- for job in jobs:
469
- try:
470
- self.log.info("{0} start repo {1}".format(self.action, job.reponame))
471
- self.name = job.reponame
472
- job.snap_unname(self.snapname)
473
- self.log.info("{0} done repo {1}".format(self.action, job.reponame))
474
- except OSRepoSyncException:
475
- self.log.fatal(
476
- "could not {0} repo {1}".format(self.action, job.reponame)
477
- )
478
- self.status = 1
479
-
480
- def do_validate(self, jobs):
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
- for job in jobs:
484
- try:
485
- self.log.info("{0} start repo {1}".format(self.action, job.reponame))
486
- packages.update(job.revalidate2())
487
- except OSRepoSyncException:
488
- self.log.fatal(
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: {0}".format(destination))
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: {0}".format(destination))
482
+ self.log.error(f"hash mismatch for: {destination}")
502
483
  except FileNotFoundError:
503
- self.log.error("file not found: {0}".format(destination))
484
+ self.log.error(f"file not found: {destination}")
504
485
 
505
486
  def run(self):
506
487
  while True:
507
488
  try:
508
- jobs = self.queue.pop()
509
- if self.action is "sync":
510
- self.do_sync(jobs)
511
- elif self.action is "snap_cleanup":
512
- self.do_snap_cleanup(jobs)
513
- elif self.action is "snap_list":
514
- self.do_snap_list(jobs)
515
- elif self.action is "snap_name":
516
- self.do_snap_name(jobs)
517
- elif self.action is "snap_unname":
518
- self.do_snap_unname(jobs)
519
- elif self.action is "snap":
520
- self.do_snap(jobs)
521
- elif self.action is "validate":
522
- self.do_validate(jobs)
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: {0}".format(hasher.hexdigest()))
57
- self.log.debug("actual hash: {0}".format(checksum))
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: {0}".format(destination))
60
+ self.log.debug(f"download valid: {destination}")
60
61
  else:
61
- self.log.error("download invalid: {0}".format(destination))
62
- raise OSRepoSyncHashError("download invalid: {0}".format(destination))
62
+ self.log.error(f"download invalid: {destination}")
63
+ raise OSRepoSyncHashError(f"download invalid: {destination}")
63
64
 
64
- def get(self, url, destination, checksum=None, hash_type=None, replace=False):
65
- self.log.info("downloading: {0}".format(url))
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
- self._get(url, destination, checksum, hash_type)
74
- self.log.info("done downloading: {0}".format(url))
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: {0}".format(url))
87
- raise OSRepoSyncDownLoadError("could not download: {0}".format(url))
105
+ self.log.error(f"could not download: {url}")
106
+ raise OSRepoSyncDownLoadError(f"could not download: {url}")
88
107
 
89
- def _get(self, url, destination, checksum=None, hash_type=None):
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
- pass
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: {0}".format(url))
143
+ self.log.info(f"successfully fetched: {url}")