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.
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/PKG-INFO +2 -2
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/ci_artifacts.py +150 -48
- checkmk_dev_tools-2.4.1/cmk_dev/py.typed +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/version.py +1 -1
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/pyproject.toml +3 -3
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/README.md +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/__init__.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/activity_from_fs.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/binreplace.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/check_rpath.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/cli.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/cpumon.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/decent_output.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/jenkins_utils/__init__.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/job_resource_usage.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/last_access.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/lockable_resources.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/procmon.py +0 -0
- {checkmk_dev_tools-2.3.0 → checkmk_dev_tools-2.4.1}/cmk_dev/pycinfo.py +0 -0
- {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
|
+
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.
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "checkmk-dev-tools"
|
|
3
|
-
version = "2.
|
|
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.
|
|
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'
|
|
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
|