checkmk-dev-tools 2.3.0__tar.gz → 2.4.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 (20) hide show
  1. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/PKG-INFO +2 -2
  2. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/ci_artifacts.py +150 -48
  3. checkmk_dev_tools-2.4.1/cmk_dev/py.typed +0 -0
  4. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/version.py +1 -1
  5. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/pyproject.toml +3 -3
  6. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/README.md +0 -0
  7. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/__init__.py +0 -0
  8. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/activity_from_fs.py +0 -0
  9. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/binreplace.py +0 -0
  10. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/check_rpath.py +0 -0
  11. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/cli.py +0 -0
  12. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/cpumon.py +0 -0
  13. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/decent_output.py +0 -0
  14. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/jenkins_utils/__init__.py +0 -0
  15. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/job_resource_usage.py +0 -0
  16. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/last_access.py +0 -0
  17. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/lockable_resources.py +0 -0
  18. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/procmon.py +0 -0
  19. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/pycinfo.py +0 -0
  20. {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: checkmk-dev-tools
3
- Version: 2.3.0
3
+ Version: 2.4.1
4
4
  Summary: Checkmk DevOps tools
5
5
  Author: Frans Fürst
6
6
  Author-email: frans.fuerst@checkmk.com
@@ -14,7 +14,7 @@ Requires-Dist: pydantic (>=2,<3)
14
14
  Requires-Dist: python-jenkins (>=1.8.3,<1.9.0)
15
15
  Requires-Dist: rich
16
16
  Requires-Dist: trickkiste (>=0.3.4,<0.4.0)
17
- Project-URL: Changelog, https://github.com/Checkmk/checkmk-dev-tools/blob/release/2.3.0/changelog.md
17
+ Project-URL: Changelog, https://github.com/Checkmk/checkmk-dev-tools/blob/release/2.4.1/changelog.md
18
18
  Project-URL: Repository, https://github.com/Checkmk/checkmk-dev-tools
19
19
  Description-Content-Type: text/markdown
20
20
 
@@ -14,8 +14,10 @@ import asyncio
14
14
  import json
15
15
  import logging
16
16
  import os
17
+ import shutil
17
18
  import sys
18
19
  import time
20
+ import zipfile
19
21
  from argparse import ArgumentParser
20
22
  from argparse import Namespace as Args
21
23
  from collections.abc import Mapping, MutableMapping, Sequence
@@ -24,7 +26,7 @@ from datetime import datetime
24
26
  from itertools import chain
25
27
  from pathlib import Path
26
28
  from subprocess import check_output
27
- from typing import Any, List, Literal, TypedDict, cast
29
+ from typing import Any, List, Literal, Set, TypedDict, cast
28
30
 
29
31
  import requests
30
32
  from influxdb_client import InfluxDBClient # type: ignore[attr-defined]
@@ -63,6 +65,7 @@ class FetchResult(TypedDict):
63
65
  artifacts: list[str]
64
66
 
65
67
  MAX_RETRY_ATTEMPTS = 3
68
+ MAX_SINGLE_FILE_DOWNLOADS = 10 # max number of single files to be downloaded before using archive.zip + decompression
66
69
 
67
70
 
68
71
  def parse_args() -> Args:
@@ -322,8 +325,6 @@ def download_artifacts(
322
325
  # pylint: disable=protected-access
323
326
  # pylint: disable=too-many-locals
324
327
 
325
- downloaded_artifacts, skipped_artifacts = [], []
326
-
327
328
  # https://bugs.launchpad.net/python-jenkins/+bug/1973243
328
329
  # https://bugs.launchpad.net/python-jenkins/+bug/2018576
329
330
 
@@ -364,6 +365,99 @@ def download_artifacts(
364
365
  p.relative_to(out_dir).as_posix() for p in out_dir.glob("**/*") if p.is_file()
365
366
  )
366
367
 
368
+ skipped_artifacts: List[str] = []
369
+ downloaded_artifacts: List[str] = []
370
+ if len(artifact_hashes) > MAX_SINGLE_FILE_DOWNLOADS:
371
+ downloaded_artifact = _download_compressed_artifacs(
372
+ client=client,
373
+ build=build,
374
+ existing_files=existing_files,
375
+ artifact_hashes=artifact_hashes,
376
+ out_dir=out_dir,
377
+ total_download_timeout=total_download_timeout,
378
+ )
379
+ downloaded_artifacts.append(downloaded_artifact)
380
+ downloaded_artifacts.extend(_decompress_artifacts(
381
+ artifact=out_dir / downloaded_artifact,
382
+ out_dir=out_dir,
383
+ ))
384
+ else:
385
+ downloaded_artifacts, skipped_artifacts, existing_files = _download_individual_artifacts(
386
+ client=client,
387
+ build=build,
388
+ existing_files=existing_files,
389
+ artifact_hashes=artifact_hashes,
390
+ out_dir=out_dir,
391
+ total_download_timeout=total_download_timeout,
392
+ )
393
+
394
+ if not no_remove_others:
395
+ for path in existing_files - set(downloaded_artifacts) - set(skipped_artifacts):
396
+ log().debug("Remove superfluous file %s", path)
397
+ with suppress(FileNotFoundError):
398
+ (out_dir / path).unlink()
399
+ log().info(
400
+ "%d artifacts available in '%s' (%d skipped, because they were up to date locally)",
401
+ len(downloaded_artifacts) + len(skipped_artifacts),
402
+ out_dir,
403
+ len(skipped_artifacts),
404
+ )
405
+
406
+ return downloaded_artifacts, skipped_artifacts
407
+
408
+ def _download_compressed_artifacs(
409
+ client: Jenkins,
410
+ build: Build,
411
+ existing_files: Set[str],
412
+ artifact_hashes: Mapping[str, str],
413
+ out_dir: Path,
414
+ total_download_timeout: int = 240,
415
+ ) -> str:
416
+
417
+ # https://ci.lan.tribe29.com/job/checkmk/job/master/job/cv/job/test-gerrit-single-k8s/710903/artifact/*zip*/archive.zip
418
+ artifact = "archive.zip"
419
+ artifact_filename = out_dir / artifact
420
+
421
+ # ignore the returned value of the function as it would be like "/*zip*/archive.zip"
422
+ _download_single_file(
423
+ client=client,
424
+ build=build,
425
+ artifact=f"/*zip*/{artifact}",
426
+ artifact_filename=artifact_filename,
427
+ total_download_timeout=total_download_timeout,
428
+ )
429
+
430
+ return artifact
431
+
432
+ def _decompress_artifacts(artifact: Path, out_dir: Path, decompressed_folder_name: str = "archive") -> List[str]:
433
+ with zipfile.ZipFile(artifact, "r") as zip_ref:
434
+ zip_ref.extractall(out_dir)
435
+
436
+ # The extracted folder is always named "archive"
437
+ archive_dir = out_dir / decompressed_folder_name
438
+
439
+ downloaded_artifacts = []
440
+
441
+ # Move all contents of the decompressed archive folder up one level
442
+ if archive_dir.exists() and archive_dir.is_dir():
443
+ for item in archive_dir.iterdir():
444
+ shutil.copytree(str(item), str(out_dir / item.name), dirs_exist_ok=True)
445
+ downloaded_artifacts += [str(f.relative_to(out_dir)) for f in (out_dir / item.name).rglob("*") if f.is_file()]
446
+
447
+ shutil.rmtree(archive_dir)
448
+
449
+ return downloaded_artifacts
450
+
451
+ def _download_individual_artifacts(
452
+ client: Jenkins,
453
+ build: Build,
454
+ existing_files: Set[str],
455
+ artifact_hashes: Mapping[str, str],
456
+ out_dir: Path,
457
+ total_download_timeout: int = 240,
458
+ ) -> tuple[List[str], List[str], Set[str]]:
459
+ downloaded_artifacts, skipped_artifacts = [], []
460
+
367
461
  for artifact in build.artifacts:
368
462
  existing_files -= {artifact}
369
463
  fp_hash = artifact_hashes[artifact]
@@ -384,54 +478,62 @@ def download_artifacts(
384
478
  fp_hash,
385
479
  )
386
480
 
387
- for attempts_left in reversed(range(MAX_RETRY_ATTEMPTS + 1)):
388
- time_start = time.time()
389
- try:
390
- with client._session.get(f"{build.url}artifact/{artifact}", stream=True) as reply:
391
- log().debug("download: %s", artifact)
392
- reply.raise_for_status()
393
- artifact_filename.parent.mkdir(parents=True, exist_ok=True)
394
- current_dl_duration = 0.0
395
- with open(artifact_filename, "wb") as out_file:
396
- for chunk in reply.iter_content(chunk_size=8192):
397
- if (
398
- current_dl_duration := (time.time() - time_start)
399
- ) > total_download_timeout:
400
- raise TimeoutError(
401
- f"Downloading of {reply.url} took longer than {total_download_timeout}s"
402
- )
403
- if chunk: # Filter out keep-alive chunks
404
- out_file.write(chunk)
405
- log().debug(
406
- "download: %s - successful (took %.2fs)", artifact, current_dl_duration
407
- )
408
- downloaded_artifacts.append(artifact)
409
- break
410
- except (
411
- requests.exceptions.ChunkedEncodingError,
412
- requests.exceptions.ConnectionError,
413
- TimeoutError,
414
- ) as exc:
415
- if not attempts_left:
416
- raise
417
- log().warning(
418
- "download_artifacts() caught %r (%s retries left)", exc, attempts_left
419
- )
481
+ downloaded_artifacts.append(
482
+ _download_single_file(
483
+ client=client,
484
+ build=build,
485
+ artifact=artifact,
486
+ artifact_filename=artifact_filename,
487
+ total_download_timeout=total_download_timeout,
488
+ )
489
+ )
420
490
 
421
- if not no_remove_others:
422
- for path in existing_files - set(downloaded_artifacts) - set(skipped_artifacts):
423
- log().debug("Remove superfluous file %s", path)
424
- with suppress(FileNotFoundError):
425
- (out_dir / path).unlink()
426
- log().info(
427
- "%d artifacts available in '%s' (%d skipped, because they were up to date locally)",
428
- len(downloaded_artifacts) + len(skipped_artifacts),
429
- out_dir,
430
- len(skipped_artifacts),
431
- )
491
+ return downloaded_artifacts, skipped_artifacts, existing_files
432
492
 
433
- return downloaded_artifacts, skipped_artifacts
493
+ def _download_single_file(
494
+ client: Jenkins,
495
+ build: Build,
496
+ artifact: str,
497
+ artifact_filename: Path,
498
+ total_download_timeout: int = 240,
499
+ ) -> str:
500
+ downloaded_artifact: str = ""
501
+
502
+ for attempts_left in reversed(range(MAX_RETRY_ATTEMPTS + 1)):
503
+ time_start = time.time()
504
+ try:
505
+ with client._session.get(f"{build.url}artifact/{artifact}", stream=True) as reply:
506
+ log().debug("download: %s", artifact)
507
+ reply.raise_for_status()
508
+ artifact_filename.parent.mkdir(parents=True, exist_ok=True)
509
+ current_dl_duration = 0.0
510
+ with open(artifact_filename, "wb") as out_file:
511
+ for chunk in reply.iter_content(chunk_size=8192):
512
+ if (
513
+ current_dl_duration := (time.time() - time_start)
514
+ ) > total_download_timeout:
515
+ raise TimeoutError(
516
+ f"Downloading of {reply.url} took longer than {total_download_timeout}s"
517
+ )
518
+ if chunk: # Filter out keep-alive chunks
519
+ out_file.write(chunk)
520
+ log().debug(
521
+ "download: %s - successful (took %.2fs)", artifact, current_dl_duration
522
+ )
523
+ downloaded_artifact = artifact
524
+ break
525
+ except (
526
+ requests.exceptions.ChunkedEncodingError,
527
+ requests.exceptions.ConnectionError,
528
+ TimeoutError,
529
+ ) as exc:
530
+ if not attempts_left:
531
+ raise
532
+ log().warning(
533
+ "download_artifacts() caught %r (%s retries left)", exc, attempts_left
534
+ )
434
535
 
536
+ return downloaded_artifact
435
537
 
436
538
  def path_hashes_match(actual: PathHashes, required: PathHashes) -> bool:
437
539
  """Returns True if two given path hash mappings are semantically equal, i.e. at least one hash
File without changes
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: UTF-8 -*-
3
3
 
4
- __version_info__ = ("2", "3", "0")
4
+ __version_info__ = ("2", "4", "1")
5
5
  __version__ = '.'.join(__version_info__)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "checkmk-dev-tools"
3
- version = "2.3.0" # will-be-updated-automatically
3
+ version = "2.4.1" # will-be-updated-automatically
4
4
  description = "Checkmk DevOps tools"
5
5
  authors = [
6
6
  "Frans Fürst <frans.fuerst@checkmk.com>",
@@ -9,12 +9,12 @@ authors = [
9
9
  "Timotheus Bachinger <timotheus.bachinger@checkmk.com>",
10
10
  ]
11
11
  readme = "README.md"
12
- packages = [{ include = "cmk_dev/**/*.py" }]
12
+ packages = [{ include = "cmk_dev/**/*.py" }, { include = "cmk_dev/py.typed" }]
13
13
  exclude = ["cmk_dev/out"]
14
14
 
15
15
  [tool.poetry.urls]
16
16
  Repository = "https://github.com/Checkmk/checkmk-dev-tools"
17
- Changelog = "https://github.com/Checkmk/checkmk-dev-tools/blob/release/2.3.0/changelog.md"
17
+ Changelog = "https://github.com/Checkmk/checkmk-dev-tools/blob/release/2.4.1/changelog.md"
18
18
 
19
19
  [tool.poetry.scripts]
20
20
  activity-from-fs = 'cmk_dev.activity_from_fs:main'