ScriptCollection 4.2.58__py3-none-any.whl → 4.2.60__py3-none-any.whl

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.
@@ -900,7 +900,7 @@ def AddImageToCustomRegistry()->int:
900
900
  sc:ScriptCollectionCore=ScriptCollectionCore()
901
901
  verbosity=int(args.verbosity)
902
902
  sc.log.loglevel=LogLevel(verbosity)
903
- sc.add_image_to_custom_docker_image_registry(args.remotehub,args.imagenameonremotehub,args.ownregistryaddress,args.imagenameonownregistry,args.tag,args.username,args.password,args.removeimagelocally)
903
+ sc.add_image_to_custom_docker_image_registry(args.remotehub,args.imagenameonremotehub,args.ownregistryaddress,args.imagenameonownregistry,args.tag,args.username,args.password)
904
904
  return 0
905
905
 
906
906
  def SyncXlfFiles()->int:
@@ -35,6 +35,12 @@ class VersionEcholon(Enum):
35
35
  LatestPatchOrLatestMinorOrNextMajor = 2
36
36
  LatestVersion = 3
37
37
 
38
+ class Platform(Enum):
39
+ Windows_AMD64 = 0
40
+ Linux_AMD64 = 1
41
+ Linux_ARM64 = 2
42
+ MacOS_ARM64 = 3
43
+
38
44
 
39
45
  class Dependency:
40
46
  dependencyname:str
@@ -1429,4 +1435,102 @@ class GeneralUtilities:
1429
1435
  result= GeneralUtilities.escape_json_property_value(str_value)
1430
1436
  #do other adaptions here if desired/required
1431
1437
  return result
1432
-
1438
+
1439
+
1440
+ @staticmethod
1441
+ @check_arguments
1442
+ def get_current_platform() -> Platform:
1443
+ system = platform.system().lower()
1444
+ machine = platform.machine().lower()
1445
+
1446
+ if system == "windows" and machine in ("x86_64", "amd64"):
1447
+ return Platform.Windows_AMD64
1448
+ elif system == "linux" and machine in ("x86_64", "amd64"):
1449
+ return Platform.Linux_AMD64
1450
+ elif system == "linux" and machine in ("arm64", "aarch64"):
1451
+ return Platform.Linux_ARM64
1452
+ elif system == "darwin" and machine in ("x86_64", "amd64"):
1453
+ return Platform.MacOS_ARM64
1454
+ else:
1455
+ raise ValueError(f"Unsupported platform: {system}/{machine}")
1456
+
1457
+ @staticmethod
1458
+ @check_arguments
1459
+ def platform_to_short_str(platform_value: Platform) -> str:
1460
+ mapping = {
1461
+ Platform.Windows_AMD64: "win-x64",
1462
+ Platform.Linux_AMD64: "linux-x64",
1463
+ Platform.Linux_ARM64: "linux-arm64",
1464
+ Platform.MacOS_ARM64: "osx-arm64",
1465
+ }
1466
+ return mapping[platform_value]
1467
+
1468
+ @staticmethod
1469
+ @check_arguments
1470
+ def platform_from_short_str(platform_str: str) -> Platform:
1471
+ mapping = {
1472
+ "win-x64": Platform.Windows_AMD64,
1473
+ "linux-x64": Platform.Linux_AMD64,
1474
+ "linux-arm64": Platform.Linux_ARM64,
1475
+ "osx-arm64": Platform.MacOS_ARM64,
1476
+ }
1477
+ if platform_str not in mapping:
1478
+ raise ValueError(f"Unsupported platform string: {platform_str}")
1479
+ return mapping[platform_str]
1480
+
1481
+ @staticmethod
1482
+ @check_arguments
1483
+ def platform_to_dash_str(platform_value: Platform) -> str:
1484
+ mapping = {
1485
+ Platform.Windows_AMD64: "Windows-x64",
1486
+ Platform.Linux_AMD64: "Linux-x64",
1487
+ Platform.Linux_ARM64: "Linux-arm64",
1488
+ Platform.MacOS_ARM64: "MacOS-arm64",
1489
+ }
1490
+ return mapping[platform_value]
1491
+
1492
+ @staticmethod
1493
+ @check_arguments
1494
+ def platform_from_dash_str(platform_str: str) -> Platform:
1495
+ mapping = {
1496
+ "Windows-x64": Platform.Windows_AMD64,
1497
+ "Linux-x64": Platform.Linux_AMD64,
1498
+ "Linux-arm64": Platform.Linux_ARM64,
1499
+ "MacOS-arm64": Platform.MacOS_ARM64,
1500
+ }
1501
+ if platform_str not in mapping:
1502
+ raise ValueError(f"Unsupported platform string: {platform_str}")
1503
+ return mapping[platform_str]
1504
+
1505
+ @staticmethod
1506
+ @check_arguments
1507
+ def platform_to_docker_platform_str(platform_value: Platform) -> str:
1508
+ mapping = {
1509
+ Platform.Windows_AMD64: "windows/amd64",
1510
+ Platform.Linux_AMD64: "linux/amd64",
1511
+ Platform.Linux_ARM64: "linux/arm64",
1512
+ Platform.MacOS_ARM64: "linux/arm64", # macOS → linux container
1513
+ }
1514
+ return mapping[platform_value]
1515
+
1516
+ @staticmethod
1517
+ @check_arguments
1518
+ def platform_to_dotnet_runtime_identifier(platform_value: Platform) -> str:
1519
+ mapping = {
1520
+ Platform.Windows_AMD64: "win-x64",
1521
+ Platform.Linux_AMD64: "linux-x64",
1522
+ Platform.Linux_ARM64: "linux-arm64",
1523
+ Platform.MacOS_ARM64: "osx-arm64",
1524
+ }
1525
+ return mapping[platform_value]
1526
+
1527
+ @staticmethod
1528
+ @check_arguments
1529
+ def get_all_platforms() -> list[Platform]:
1530
+ return [
1531
+ Platform.Windows_AMD64,
1532
+ Platform.Linux_AMD64,
1533
+ Platform.Linux_ARM64,
1534
+ Platform.MacOS_ARM64,
1535
+ ]
1536
+
@@ -31,12 +31,12 @@ import qrcode
31
31
  import pycdlib
32
32
  import send2trash
33
33
  from pypdf import PdfReader, PdfWriter
34
- from .GeneralUtilities import GeneralUtilities
34
+ from .GeneralUtilities import GeneralUtilities,Platform
35
35
  from .ProgramRunnerBase import ProgramRunnerBase
36
36
  from .ProgramRunnerPopen import ProgramRunnerPopen
37
37
  from .SCLog import SCLog, LogLevel
38
38
 
39
- version = "4.2.58"
39
+ version = "4.2.60"
40
40
  __version__ = version
41
41
 
42
42
  class VSCodeWorkspaceShellTask:
@@ -215,29 +215,49 @@ class ScriptCollectionCore:
215
215
 
216
216
  def registry_contains_image(self,registry_url:str,image:str,registry_username:str,registry_password:str)->bool:
217
217
  """This function assumes that the registry is a custom deployed docker-registry (see https://hub.docker.com/_/registry )"""
218
- if "/" in image:
219
- image=image.rsplit("/", 1)[-1]
220
- registry_username,registry_password=self.__load_credentials_if_required_and_available(registry_url,registry_username,registry_password)
221
- catalog_url = f"{registry_url}/v2/_catalog"
222
- response = requests.get(catalog_url, auth=(registry_username, registry_password),timeout=20)
223
- response.raise_for_status() # check if statuscode = 200
224
- data = response.json()
225
- # expected: {"repositories": ["nginx", "myapp"]}
226
- images = data.get("repositories", [])
227
- result=image in images
228
- return result
218
+ try:
219
+ if "/" in image:
220
+ image=image.rsplit("/", 1)[-1]
221
+ registry_username,registry_password=self.__load_credentials_if_required_and_available(registry_url,registry_username,registry_password)
222
+ catalog_url = f"{registry_url}/v2/_catalog"
223
+ response = requests.get(catalog_url, auth=(registry_username, registry_password),timeout=20)
224
+ response.raise_for_status() # check if statuscode = 200
225
+ data = response.json()
226
+ # expected: {"repositories": ["nginx", "myapp"]}
227
+ images = data.get("repositories", [])
228
+ if not (image in images):
229
+ return False
230
+
231
+ if self.get_tags_of_images_from_registry(registry_url,image,registry_username,registry_password)<1:
232
+ return False
233
+
234
+ return True
235
+ except Exception:
236
+ return False
229
237
 
230
- @GeneralUtilities.check_arguments
231
- def add_image_to_custom_docker_image_registry(self,remote_hub:str,imagename_on_remote_hub:str,own_registry_address:str,imagename_on_own_registry:str,tag:str,registry_username:str,registry_password:str,remove_locally_in_the_end:bool)->None:
232
- registry_username,registry_password=self.__load_credentials_if_required_and_available(remote_hub,registry_username,registry_password)
233
- source_address=f"{remote_hub}/{imagename_on_remote_hub}:{tag}"
234
- target_address=f"{own_registry_address}/{imagename_on_own_registry}:{tag}"
235
- self.run_program("docker",f"pull {source_address}")
236
- self.run_program("docker",f"tag {source_address} {target_address}")
237
- self.run_program("docker",f"push {target_address}")
238
- if remove_locally_in_the_end:
239
- self.run_program("docker",f"rmi {source_address}")
240
- self.run_program("docker",f"rmi {target_address}")
238
+ def docker_platform_to_slug(self,platform_value: Platform) -> str:
239
+ if platform_value == Platform.Linux_AMD64:
240
+ return "linux-amd64"
241
+ elif platform_value == Platform.Linux_ARM64:
242
+ return "linux-arm64"
243
+ raise ValueError(f"Unsupported platform: {platform_value}")
244
+
245
+ @GeneralUtilities.check_arguments
246
+ def add_image_to_custom_docker_image_registry(
247
+ self,
248
+ remote_hub: str,
249
+ imagename_on_remote_hub: str,
250
+ own_registry_address: str,
251
+ imagename_on_own_registry: str,
252
+ tag: str,
253
+ registry_username: str,
254
+ registry_password: str,
255
+ ) -> None:
256
+ registry_username, registry_password = self.__load_credentials_if_required_and_available(remote_hub, registry_username, registry_password)
257
+ source_address = f"{remote_hub}/{imagename_on_remote_hub}:{tag}"
258
+ target_address = f"{own_registry_address}/{imagename_on_own_registry}:{tag}"
259
+ self.run_program("docker", f"buildx imagetools create --tag {target_address} {source_address}")#this does pull and push for each platform
260
+
241
261
 
242
262
  def get_tags_of_images_from_registry(self,registry_base_url:str,image:str,registry_username:str,registry_password:str)->list[str]:
243
263
  """registry_base_url must be in the format 'https://myregistry.example.com'
@@ -2870,6 +2890,7 @@ OCR-content:
2870
2890
  if remove_images:
2871
2891
  self.run_program_with_retry("docker","image prune -a -f",amount_of_attempts=amount_of_attempts)
2872
2892
  self.run_program_with_retry("docker","builder prune -a -f",amount_of_attempts=amount_of_attempts)
2893
+ self.run_program_with_retry("docker","buildx prune -f",amount_of_attempts=amount_of_attempts,throw_exception_if_exitcode_is_not_zero=False) # buildx prune is not available on every machine.
2873
2894
  self.run_program_with_retry("docker","system df",print_live_output=self.log.loglevel==LogLevel.Debug,amount_of_attempts=amount_of_attempts)
2874
2895
 
2875
2896
  @GeneralUtilities.check_arguments
@@ -3202,9 +3223,11 @@ OCR-content:
3202
3223
  return results[0]["language"]
3203
3224
 
3204
3225
  @GeneralUtilities.check_arguments
3205
- def get_all_files_in_git_repository(self,repository_folder:str,include_submodules: bool = True) -> list[str]:
3206
- """returns all files in a git-repository except ignored files"""
3207
- cmd = ["ls-files", "--cached", "--exclude-standard"]
3226
+ def get_all_files_in_git_repository(self,repository_folder:str,ignore_ignored_files:bool=True,include_submodules: bool = True) -> list[str]:
3227
+ """Returns a list of all files in a git-repository."""
3228
+ cmd = ["ls-files", "--cached"]
3229
+ if ignore_ignored_files:
3230
+ cmd.append("--exclude-standard")
3208
3231
  if include_submodules:
3209
3232
  cmd.append("--recurse-submodules")
3210
3233
  output=self.run_program_argsasarray("git", cmd,repository_folder)
@@ -3212,10 +3235,29 @@ OCR-content:
3212
3235
  return files
3213
3236
 
3214
3237
  @GeneralUtilities.check_arguments
3215
- def write_file_list_for_repository(self,repository_folder:str,target_file:str="./FileList.txt") -> None:
3238
+ def write_file_list_for_repository(self,repository_folder:str,target_file:str="./FileList.txt",ignore_ignored_files:bool=True,include_submodules: bool = True) -> None:
3216
3239
  if os.path.isabs(target_file):
3217
3240
  target_file=GeneralUtilities.resolve_relative_path(target_file,repository_folder)
3218
3241
  target_file=GeneralUtilities.normalize_path(target_file)
3219
- files=self.get_all_files_in_git_repository(repository_folder)
3242
+ files=self.get_all_files_in_git_repository(repository_folder,ignore_ignored_files,include_submodules)
3220
3243
  GeneralUtilities.ensure_file_exists(target_file)
3221
3244
  GeneralUtilities.write_lines_to_file(target_file, files)
3245
+
3246
+ @GeneralUtilities.check_arguments
3247
+ def get_all_commits_in_git_repository(self,repository_folder:str,include_all_heads:bool=False) -> list[str]:
3248
+ """Returns a textual visualization of all commits in a git-repository."""
3249
+ #do 'git log --reverse --all --pretty=format:"%ci | %H | %cn <%ce> | %s"'
3250
+ args = ["log", "--reverse", "--pretty=format:%ci | %H | %cn <%ce> | %s"]
3251
+ if include_all_heads:
3252
+ args.append("--all")
3253
+ result=self.run_program_argsasarray("git", args, repository_folder, throw_exception_if_exitcode_is_not_zero=True)
3254
+ return result[1]
3255
+
3256
+ @GeneralUtilities.check_arguments
3257
+ def write_commit_list_for_repository(self,repository_folder:str,target_file:str,include_all_heads:bool=False) -> None:
3258
+ if os.path.isabs(target_file):
3259
+ target_file=GeneralUtilities.resolve_relative_path(target_file,repository_folder)
3260
+ target_file=GeneralUtilities.normalize_path(target_file)
3261
+ commits=self.get_all_commits_in_git_repository(repository_folder, include_all_heads)
3262
+ GeneralUtilities.ensure_file_exists(target_file)
3263
+ GeneralUtilities.write_lines_to_file(target_file, commits)
@@ -3,7 +3,7 @@ from urllib import request
3
3
  import time
4
4
  import ssl
5
5
  from datetime import timedelta,datetime
6
- from ...GeneralUtilities import GeneralUtilities
6
+ from ...GeneralUtilities import GeneralUtilities, Platform
7
7
  from ...SCLog import LogLevel
8
8
  from ..TFCPS_CodeUnitSpecific_Base import TFCPS_CodeUnitSpecific_Base,TFCPS_CodeUnitSpecific_Base_CLI
9
9
 
@@ -13,32 +13,36 @@ class TFCPS_CodeUnitSpecific_Docker_Functions(TFCPS_CodeUnitSpecific_Base):
13
13
  def __init__(self,current_file:str,verbosity:LogLevel,targetenvironmenttype:str,use_cache:bool,is_pre_merge:bool):
14
14
  super().__init__(current_file, verbosity,targetenvironmenttype,use_cache,is_pre_merge)
15
15
 
16
+
16
17
  @GeneralUtilities.check_arguments
17
- def build(self,custom_arguments:dict[str,str]) -> None:
18
+ def build(self,platforms:list[Platform],custom_arguments:dict[str,str]) -> None:
18
19
  codeunitname: str =self.get_codeunit_name()
19
- codeunit_folder =self.get_codeunit_folder()
20
+ codeunit_folder =self.get_codeunit_folder()
20
21
  codeunitname_lower = codeunitname.lower()
21
22
  codeunit_file =self.get_codeunit_file()
22
23
  codeunitversion = self.tfcps_Tools_General.get_version_of_codeunit(codeunit_file)
23
- args = ["image", "build", "--pull", "--force-rm", "--progress=plain", "--build-arg", f"TargetEnvironmentType={self.get_target_environment_type()}", "--build-arg", f"CodeUnitName={codeunitname}", "--build-arg", f"CodeUnitVersion={codeunitversion}", "--build-arg", f"CodeUnitOwnerName={self.tfcps_Tools_General.get_codeunit_owner_name(self.get_codeunit_file())}", "--build-arg", f"CodeUnitOwnerEMailAddress={self.tfcps_Tools_General.get_codeunit_owner_emailaddress(self.get_codeunit_file())}"]
24
24
  if custom_arguments is None:
25
25
  custom_arguments=dict[str,str]()
26
- for custom_argument_key, custom_argument_value in custom_arguments.items():
27
- args.append("--build-arg")
28
- args.append(f"{custom_argument_key}={custom_argument_value}")
29
- args = args+["--tag", f"{codeunitname_lower}:latest", "--tag", f"{codeunitname_lower}:{codeunitversion}", "--file", f"{codeunitname}/Dockerfile"]
30
- if not self.use_cache():
31
- args.append("--no-cache")
32
- args.append(".")
33
- codeunit_content_folder = os.path.join(codeunit_folder)
34
- GeneralUtilities.retry_action(lambda: self._protected_sc.run_program_argsasarray("docker", args, codeunit_content_folder, print_errors_as_information=True), 3)
35
26
  artifacts_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts", codeunit_folder)
36
27
  app_artifacts_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
37
- GeneralUtilities.ensure_directory_does_not_exist(app_artifacts_folder)
38
- GeneralUtilities.ensure_directory_exists(app_artifacts_folder)
39
- self._protected_sc.run_program_argsasarray("docker", ["save", "--output", f"{codeunitname}_v{codeunitversion}.tar", f"{codeunitname_lower}:{codeunitversion}"], app_artifacts_folder, print_errors_as_information=True)
40
- self.copy_source_files_to_output_directory()
28
+ GeneralUtilities.ensure_folder_exists_and_is_empty(app_artifacts_folder)
29
+ for platform in platforms:
30
+ #builder must be created once before with "docker buildx create --use"
31
+ args = ["buildx","build", "--platform",GeneralUtilities.platform_to_docker_platform_str(platform), "--pull", "--force-rm", "--progress=plain", "--build-arg", f"TargetEnvironmentType={self.get_target_environment_type()}", "--build-arg", f"CodeUnitName={codeunitname}", "--build-arg", f"CodeUnitVersion={codeunitversion}", "--build-arg", f"CodeUnitOwnerName={self.tfcps_Tools_General.get_codeunit_owner_name(self.get_codeunit_file())}", "--build-arg", f"CodeUnitOwnerEMailAddress={self.tfcps_Tools_General.get_codeunit_owner_emailaddress(self.get_codeunit_file())}", "--build-arg", f"Platform={GeneralUtilities.platform_to_dash_str(platform)}", "--build-arg", f"DotNetRuntime={GeneralUtilities.platform_to_dotnet_runtime_identifier(platform)}"]
32
+ for custom_argument_key, custom_argument_value in custom_arguments.items():
33
+ args.append("--build-arg")
34
+ args.append(f"{custom_argument_key}={custom_argument_value}")
35
+ args = args+["--tag", f"{codeunitname_lower}:latest", "--tag", f"{codeunitname_lower}:{codeunitversion}", "--file", f"{codeunitname}/Dockerfile"]
36
+ if not self.use_cache():
37
+ args.append("--no-cache")
38
+ args.append("--load")
39
+ args.append(".")
40
+ codeunit_content_folder = codeunit_folder
41
+ GeneralUtilities.retry_action(lambda a=args, f=codeunit_content_folder: self._protected_sc.run_program_argsasarray("docker", a, f, print_errors_as_information=True), 3)
42
+
43
+ self._protected_sc.run_program_argsasarray("docker", ["save", "--output", f"{codeunitname}_v{codeunitversion}_{GeneralUtilities.platform_to_dash_str(platform)}.tar", f"{codeunitname_lower}:{codeunitversion}"], app_artifacts_folder, print_errors_as_information=True)
41
44
  self.__generate_sbom_for_docker_image()
45
+ self.copy_source_files_to_output_directory()
42
46
 
43
47
 
44
48
  @GeneralUtilities.check_arguments
@@ -93,10 +97,22 @@ class TFCPS_CodeUnitSpecific_Docker_Functions(TFCPS_CodeUnitSpecific_Base):
93
97
  timeout=timedelta(seconds=120)
94
98
  if environment_variables is None:
95
99
  environment_variables={}
100
+ current_platform = GeneralUtilities.get_current_platform()
101
+ platform_for_test:Platform=None
102
+ if current_platform == Platform.Windows_AMD64:
103
+ platform_for_test=Platform.Linux_AMD64
104
+ elif current_platform == Platform.Linux_AMD64:
105
+ platform_for_test=Platform.Linux_AMD64
106
+ elif current_platform == Platform.Linux_ARM64:
107
+ platform_for_test=Platform.Linux_ARM64
108
+ elif current_platform == Platform.MacOS_ARM64:
109
+ platform_for_test=Platform.Linux_ARM64
110
+ else:
111
+ raise ValueError(f"Current platform {current_platform} is not supported for testing.")
96
112
  oci_image_artifacts_folder :str= GeneralUtilities.resolve_relative_path("Other/Artifacts/BuildResult_OCIImage", self.get_codeunit_folder())
97
113
  container_name:str=f"{self.get_codeunit_name()}finaltest".lower()
98
114
  self.tfcps_Tools_General.ensure_containers_are_not_running([container_name])
99
- self.tfcps_Tools_General.load_docker_image(oci_image_artifacts_folder)
115
+ self.tfcps_Tools_General.load_docker_image(oci_image_artifacts_folder,platform_for_test)
100
116
  codeunit_file:str=os.path.join(self.get_codeunit_folder(),f"{self.get_codeunit_name()}.codeunit.xml")
101
117
  image=f"{self.get_codeunit_name()}:{self.tfcps_Tools_General.get_version_of_codeunit(codeunit_file)}".lower()
102
118
  argument=f"run -d --name {container_name}"
@@ -23,8 +23,7 @@ class TFCPS_CodeUnitSpecific_Python_Functions(TFCPS_CodeUnitSpecific_Base):
23
23
  def generate_bom_for_python_project(self) -> None:
24
24
  codeunit_folder: str=self.get_codeunit_folder()
25
25
  codeunitname: str=self.get_codeunit_name()
26
- repository_folder = os.path.dirname(codeunit_folder)
27
-
26
+ repository_folder = os.path.dirname(codeunit_folder)
28
27
  codeunitversion = self.tfcps_Tools_General.get_version_of_codeunit(self.get_codeunit_file())
29
28
  bom_folder = "Other/Artifacts/BOM"
30
29
  bom_folder_full = os.path.join(codeunit_folder, bom_folder)
@@ -40,7 +39,7 @@ class TFCPS_CodeUnitSpecific_Python_Functions(TFCPS_CodeUnitSpecific_Base):
40
39
 
41
40
  GeneralUtilities.ensure_file_exists(bom_file_json)
42
41
  GeneralUtilities.write_text_to_file(bom_file_json, result[1])
43
- cyclonedx_exe=self.tfcps_Tools_General.ensure_cyclonedxcli_is_available(repository_folder,not self.use_cache())
42
+ cyclonedx_exe=self.tfcps_Tools_General.ensure_cyclonedxcli_is_available(not self.use_cache())
44
43
  self._protected_sc.run_program(cyclonedx_exe, f"convert --input-file ./{codeunitname}/{bom_file_relative_json} --input-format json --output-file ./{codeunitname}/{bom_file_relative_xml} --output-format xml", repository_folder)
45
44
  self._protected_sc.format_xml_file(bom_file_xml)
46
45
  GeneralUtilities.ensure_file_does_not_exist(bom_file_json)
@@ -16,7 +16,7 @@ import urllib.request
16
16
  from packaging import version
17
17
  import requests
18
18
  from lxml import etree
19
- from ..GeneralUtilities import GeneralUtilities
19
+ from ..GeneralUtilities import GeneralUtilities,Platform
20
20
  from ..ScriptCollectionCore import ScriptCollectionCore,VSCodeWorkspaceShellTask
21
21
  from ..SCLog import LogLevel
22
22
  from ..OCIImages.AbstractImageHandler import AbstractImageHandler
@@ -38,7 +38,7 @@ class TFCPS_Tools_General:
38
38
  return GeneralUtilities.string_to_boolean(str(root.xpath('//cps:codeunit/@enabled', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
39
39
 
40
40
  @GeneralUtilities.check_arguments
41
- def ensure_cyclonedxcli_is_available(self, target_folder: str,enforce_update:bool) -> str:
41
+ def ensure_cyclonedxcli_is_available(self,enforce_update:bool) -> str:
42
42
  local_filename = "cyclonedx-cli"
43
43
  filename_on_github: str
44
44
  if GeneralUtilities.current_system_is_windows():
@@ -46,20 +46,20 @@ class TFCPS_Tools_General:
46
46
  local_filename = local_filename+".exe"
47
47
  else:
48
48
  filename_on_github = "cyclonedx-linux-x64"
49
- return self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "CycloneDX", "cyclonedx-cli", "CycloneDXCLI", local_filename, lambda latest_version: filename_on_github,enforce_update=enforce_update)
49
+ return self.ensure_file_from_github_assets_is_available_with_retry("CycloneDX", "cyclonedx-cli", "CycloneDXCLI",local_filename,lambda latest_version: filename_on_github,enforce_update=enforce_update)
50
50
 
51
51
  @GeneralUtilities.check_arguments
52
- def ensure_file_from_github_assets_is_available_with_retry(self, target_folder: str, githubuser: str, githubprojectname: str, resource_name: str, local_filename: str, get_filename_on_github, amount_of_attempts: int = 5,enforce_update:bool=False) -> str:
53
- return GeneralUtilities.retry_action(lambda: self.ensure_file_from_github_assets_is_available(target_folder, githubuser, githubprojectname, resource_name, local_filename, get_filename_on_github,enforce_update), amount_of_attempts)
52
+ def ensure_file_from_github_assets_is_available_with_retry(self, githubuser: str, githubprojectname: str, local_resource_name: str, local_filename: str, get_filename_on_github, amount_of_attempts: int = 5,enforce_update:bool=False) -> str:
53
+ return GeneralUtilities.retry_action(lambda: self.ensure_file_from_github_assets_is_available(githubuser, githubprojectname, local_resource_name, local_filename, get_filename_on_github,enforce_update), amount_of_attempts)
54
54
 
55
55
  @GeneralUtilities.check_arguments
56
- def ensure_file_from_github_assets_is_available(self, target_folder: str, githubuser: str, githubprojectname: str, resource_name: str, local_filename: str, get_filename_on_github,enforce_update:bool) -> str:
56
+ def ensure_file_from_github_assets_is_available(self,githubuser: str, githubprojectname: str, local_resource_name: str, local_filename: str, get_filename_on_github,enforce_update:bool) -> str:
57
57
  #TODO use or remove target_folder-parameter
58
- resource_folder =os.path.join( self.__sc.get_global_cache_folder(),"Tools",resource_name)
58
+ resource_folder =os.path.join( self.__sc.get_global_cache_folder(),"Tools",local_resource_name)
59
59
  file = f"{resource_folder}/{local_filename}"
60
60
  file_exists = os.path.isfile(file)
61
61
  if not file_exists:
62
- self.__sc.log.log(f"Download Asset \"{githubuser}/{githubprojectname}: {resource_name}\" from GitHub to global cache...", LogLevel.Information)
62
+ self.__sc.log.log(f"Download Asset \"{githubuser}/{githubprojectname}: {local_resource_name}\" from GitHub to global cache...", LogLevel.Information)
63
63
  GeneralUtilities.ensure_folder_exists_and_is_empty(resource_folder)
64
64
  headers = { 'User-Agent': 'Mozilla/5.0'}
65
65
  self.__add_github_api_key_if_available(headers)
@@ -459,7 +459,7 @@ class TFCPS_Tools_General:
459
459
  target_original_sbom_file_relative = os.path.dirname(target_sbom_file_relative)+"/"+os.path.basename(target_sbom_file_relative)+".original.xml"
460
460
  os.rename(os.path.join(repository_folder, target_sbom_file_relative), os.path.join(repository_folder, target_original_sbom_file_relative))
461
461
 
462
- cyclonedx_exe:str=self.ensure_cyclonedxcli_is_available(repository_folder,not use_cache)
462
+ cyclonedx_exe:str=self.ensure_cyclonedxcli_is_available(not use_cache)
463
463
  self.__sc.run_program(cyclonedx_exe, f"merge --input-files {source_sbom_file_relative} {target_original_sbom_file_relative} --output-file {target_sbom_file_relative}", repository_folder)
464
464
  GeneralUtilities.ensure_file_does_not_exist(os.path.join(repository_folder, target_original_sbom_file_relative))
465
465
  self.__sc.format_xml_file(os.path.join(repository_folder, target_sbom_file_relative))
@@ -496,21 +496,20 @@ class TFCPS_Tools_General:
496
496
  def generate_svg_files_from_plantuml_files_for_repository(self, repository_folder: str,use_cache:bool) -> None:
497
497
  self.__sc.log.log("Generate svg-files from plantuml-files...")
498
498
  self.__sc.assert_is_git_repository(repository_folder)
499
- plantuml_jar_file=self.ensure_plantuml_is_available(repository_folder,not use_cache)
499
+ plantuml_jar_file=self.ensure_plantuml_is_available(not use_cache)
500
500
  target_folder = os.path.join(repository_folder, "Other", "Reference")
501
501
  self.__generate_svg_files_from_plantuml(target_folder, plantuml_jar_file)
502
502
 
503
503
  @GeneralUtilities.check_arguments
504
504
  def generate_svg_files_from_plantuml_files_for_codeunit(self, codeunit_folder: str,use_cache:bool) -> None:
505
505
  self.assert_is_codeunit_folder(codeunit_folder)
506
- repository_folder = os.path.dirname(codeunit_folder)
507
- plantuml_jar_file=self.ensure_plantuml_is_available(repository_folder,not use_cache)
506
+ plantuml_jar_file=self.ensure_plantuml_is_available(not use_cache)
508
507
  target_folder = os.path.join(codeunit_folder, "Other", "Reference")
509
508
  self.__generate_svg_files_from_plantuml(target_folder, plantuml_jar_file)
510
509
 
511
510
  @GeneralUtilities.check_arguments
512
- def ensure_plantuml_is_available(self, target_folder: str,enforce_update:bool) -> str:
513
- return self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "plantuml", "plantuml", "PlantUML", "plantuml.jar", lambda latest_version: "plantuml.jar",enforce_update=enforce_update)
511
+ def ensure_plantuml_is_available(self,enforce_update:bool) -> str:
512
+ return self.ensure_file_from_github_assets_is_available_with_retry("plantuml", "plantuml", "PlantUML", "plantuml.jar", lambda latest_version: "plantuml.jar",enforce_update=enforce_update)
514
513
 
515
514
  @GeneralUtilities.check_arguments
516
515
  def __generate_svg_files_from_plantuml(self, diagrams_files_folder: str, plantuml_jar_file: str) -> None:
@@ -578,7 +577,7 @@ class TFCPS_Tools_General:
578
577
  target_folder_extracted = os.path.join(self.__sc.get_global_cache_folder(),"Tools",resource_name)
579
578
  update:bool=not os.path.isdir(target_folder_extracted) or GeneralUtilities.folder_is_empty(target_folder_extracted) or enforce_update
580
579
  if update:
581
- downloaded_file=self.ensure_file_from_github_assets_is_available_with_retry(target_folder_unextracted, "trufflesecurity", "trufflehog", resource_name+"_Unextracted", zip_filename, lambda latest_version: f"trufflehog_{latest_version[1:]}_{osname_in_github_asset}_amd64.tar.gz",enforce_update=enforce_update)
580
+ downloaded_file=self.ensure_file_from_github_assets_is_available_with_retry( "trufflesecurity", "trufflehog", resource_name+"_Unextracted", zip_filename, lambda latest_version: f"trufflehog_{latest_version[1:]}_{osname_in_github_asset}_amd64.tar.gz",enforce_update=enforce_update)
582
581
  #TODO add option to also download arm-version
583
582
  local_zip_file: str = downloaded_file
584
583
  GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder_extracted)
@@ -639,33 +638,50 @@ class TFCPS_Tools_General:
639
638
 
640
639
  @GeneralUtilities.check_arguments
641
640
  def ensure_androidappbundletool_is_available(self, target_folder: str,enforce_update:bool) -> str:
642
- return self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "google", "bundletool", "AndroidAppBundleTool", "bundletool.jar", lambda latest_version: f"bundletool-all-{latest_version}.jar",enforce_update=enforce_update)
641
+ return self.ensure_file_from_github_assets_is_available_with_retry( "google", "bundletool", "AndroidAppBundleTool", "bundletool.jar", lambda latest_version: f"bundletool-all-{latest_version}.jar",enforce_update=enforce_update)
643
642
 
644
643
  @GeneralUtilities.check_arguments
645
644
  def ensure_mediamtx_is_available(self, target_folder: str,enforce_update:bool) -> None:
646
- def download_and_extract(osname: str, osname_in_github_asset: str, extension: str):
647
- resource_name: str = f"MediaMTX_{osname}"
648
- zip_filename: str = f"{resource_name}.{extension}"
645
+ def download_and_extract(osname: str, osname_in_github_asset: str, extension: str,architecture:Platform):
646
+ resource_name: str = f"MediaMTX_{GeneralUtilities.platform_to_dash_str(architecture)}"
649
647
  resource_folder: str = os.path.join(target_folder, "Other", "Resources", resource_name)
650
648
  target_folder_extracted = os.path.join(resource_folder, "MediaMTX")
651
649
  update:bool=not os.path.isdir(target_folder_extracted) or GeneralUtilities.folder_is_empty(target_folder_extracted) or enforce_update
652
650
  if update:
653
- self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "bluenviron", "mediamtx", resource_name, zip_filename, lambda latest_version: f"mediamtx_{latest_version}_{osname_in_github_asset}_amd64.{extension}",enforce_update=enforce_update)
654
- local_zip_file: str = os.path.join(resource_folder, f"{resource_name}.{extension}")
651
+ platform_str:str=None
652
+ match architecture:
653
+ case Platform.Windows_AMD64:
654
+ platform_str = "windows_amd64"
655
+ case Platform.Linux_ARM64:
656
+ platform_str = "linux_arm64"
657
+ case Platform.Linux_AMD64:
658
+ platform_str = "linux_amd64"
659
+ case Platform.MacOS_ARM64:
660
+ platform_str = "darwin_arm64"
661
+ case _:
662
+ raise ValueError(f"Unknown platform: {str(architecture)}")
663
+
664
+ resource_filename_name_remote:str=f"mediamtx_{platform_str}.{extension}"
665
+ resource_name_local:str=f"MediaMTCX_{platform_str}"
666
+ global_cache_file=os.path.join( self.__sc.get_global_cache_folder(),"Tools",resource_name_local,resource_filename_name_remote)
667
+ if (not os.path.isfile( global_cache_file )) or enforce_update:
668
+ self.ensure_file_from_github_assets_is_available_with_retry( "bluenviron", "mediamtx", resource_name_local, resource_filename_name_remote, lambda latest_version: f"mediamtx_{latest_version}_{platform_str}.{extension}",enforce_update=enforce_update)
669
+ GeneralUtilities.assert_file_exists(global_cache_file)
670
+ GeneralUtilities.assert_file_exists(global_cache_file)
655
671
  GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder_extracted)
656
672
  if extension == "zip":
657
- with zipfile.ZipFile(local_zip_file, 'r') as zip_ref:
673
+ with zipfile.ZipFile(global_cache_file, 'r') as zip_ref:
658
674
  zip_ref.extractall(target_folder_extracted)
659
675
  elif extension == "tar.gz":
660
- with tarfile.open(local_zip_file, "r:gz") as tar:
676
+ with tarfile.open(global_cache_file, "r:gz") as tar:
661
677
  tar.extractall(path=target_folder_extracted)
662
678
  else:
663
679
  raise ValueError(f"Unknown extension: \"{extension}\"")
664
- GeneralUtilities.ensure_file_does_not_exist(local_zip_file)
665
680
 
666
- download_and_extract("Windows", "windows", "zip")
667
- download_and_extract("Linux", "linux", "tar.gz")
668
- download_and_extract("MacOS", "darwin", "tar.gz")
681
+ download_and_extract("Windows", "windows", "zip",Platform.Windows_AMD64)
682
+ download_and_extract("Linux", "linux", "tar.gz",Platform.Linux_AMD64)
683
+ download_and_extract("Linux", "linux", "tar.gz",Platform.Linux_ARM64)
684
+ download_and_extract("MacOS", "darwin", "tar.gz",Platform.MacOS_ARM64)
669
685
 
670
686
  @GeneralUtilities.check_arguments
671
687
  def clone_repository_as_resource(self, local_repository_folder: str, remote_repository_link: str, resource_name: str, repository_subname: str = None,use_cache:bool=True) -> None:
@@ -1144,7 +1160,6 @@ class TFCPS_Tools_General:
1144
1160
  if write_to_file:
1145
1161
  GeneralUtilities.write_text_to_file(version_file, latest_version_function)
1146
1162
 
1147
-
1148
1163
  @GeneralUtilities.check_arguments
1149
1164
  def push_docker_build_artifact(self, push_artifacts_file: str, registry: str, push_readme: bool, repository_folder_name: str, remote_image_name: str = None) -> None:
1150
1165
  folder_of_this_file = os.path.dirname(push_artifacts_file)
@@ -1154,33 +1169,87 @@ class TFCPS_Tools_General:
1154
1169
  if match := re.search(filename_regex, filename, re.IGNORECASE):
1155
1170
  codeunitname = match.group(1)
1156
1171
  else:
1157
- raise ValueError(f"Expected push-artifacts-file to match the regex \"{filename_regex}\" where \"{codeunitname_regex}\" represents the codeunit-name.")
1158
-
1172
+ raise ValueError(f"Expected push-artifacts-file to match the regex \"{filename_regex}\" where \"{codeunitname_regex}\" represents the codeunit-name.")
1159
1173
  repository_folder = GeneralUtilities.resolve_relative_path(f"..{os.path.sep}..{os.path.sep}Submodules{os.path.sep}{repository_folder_name}", folder_of_this_file)
1160
1174
  codeunit_folder = os.path.join(repository_folder, codeunitname)
1161
1175
  artifacts_folder = os.path.join(repository_folder,codeunitname, "Other", "Artifacts")
1162
1176
  applicationimage_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
1163
- image_file = self.__sc.find_file_by_extension(applicationimage_folder, "tar")
1164
- image_filename = os.path.basename(image_file)
1165
1177
  codeunit_version = self.get_version_of_codeunit(os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml"))
1166
1178
  if remote_image_name is None:
1167
1179
  remote_image_name = codeunitname
1168
- remote_image_name = remote_image_name.lower()
1169
- local_image_name = codeunitname.lower()
1170
- remote_repo = f"{registry}/{remote_image_name}"
1171
- remote_image_latest = f"{remote_repo}:latest"
1172
- remote_image_version = f"{remote_repo}:{codeunit_version}"
1173
- self.__sc.log.log("Load image...")
1174
- self.__sc.run_program("docker", f"load --input {image_filename}", applicationimage_folder)
1175
- self.__sc.log.log("Tag image...")
1176
- self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{codeunit_version} {remote_image_latest}")
1177
- self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{codeunit_version} {remote_image_version}")
1178
- self.__sc.log.log("Push image...")
1179
- self.__sc.run_program_with_retry("docker", f"push {remote_image_latest}")
1180
- self.__sc.run_program_with_retry("docker", f"push {remote_image_version}")
1180
+ tar_files=[f for f in GeneralUtilities.get_direct_files_of_folder(applicationimage_folder) if f.endswith(".tar")]
1181
+ target_image_address=f"{registry}/{remote_image_name}"
1182
+ tar_files_with_platforms: list[tuple[str, str, str]] = []
1183
+ for tar_file in tar_files:
1184
+ filename=os.path.basename(tar_file)#filename looks like "{codeunitname}_v{codeunitversion}_{GeneralUtilities.platform_to_dash_str(platform)}.tar"
1185
+ platform:Platform=self.platform_from_filename(filename)#GeneralUtilities.platform_from_dash_str( filename.split("_")[-1].split(".")[0])
1186
+ platform_os_in_docker_format :str = None
1187
+ platform_arch_in_docker_format :str = None
1188
+ if platform==Platform.Windows_AMD64:
1189
+ raise NotImplementedError("Building docker images for Windows is not implemented yet.")
1190
+ elif platform==Platform.Linux_AMD64:
1191
+ platform_os_in_docker_format = "linux"
1192
+ platform_arch_in_docker_format = "amd64"
1193
+ elif platform==Platform.Linux_ARM64:
1194
+ platform_os_in_docker_format = "linux"
1195
+ platform_arch_in_docker_format = "arm64"
1196
+ elif platform==Platform.MacOS_ARM64:
1197
+ raise NotImplementedError("Building docker images for MacOS is not implemented yet.")
1198
+ else:
1199
+ raise ValueError(f"Unsupported platform {platform} extracted from filename {filename}.")
1200
+ tar_files_with_platforms.append((tar_file, platform_os_in_docker_format, platform_arch_in_docker_format))
1201
+ self.push_docker_build_artifact_as_multi_arch_artifact(tar_files_with_platforms,target_image_address, "v"+codeunit_version)
1202
+ self.push_docker_build_artifact_as_multi_arch_artifact(tar_files_with_platforms,target_image_address, "latest")
1181
1203
  if push_readme:
1182
- self.__sc.run_program_with_retry("docker-pushrm", f"{remote_repo}", codeunit_folder)
1183
-
1204
+ self.__sc.run_program_with_retry("docker-pushrm", target_image_address, codeunit_folder)
1205
+
1206
+ def push_docker_build_artifact_as_multi_arch_artifact(self,tar_files: list[tuple[str, str, str]], image_address: str, tag: str):
1207
+ """
1208
+ tar_files: list of (tar_path, os, arch) tuples
1209
+ for example [
1210
+ ("MyApp.Linux.arm64.tar", "linux", "arm64"),
1211
+ ("MyApp.Linux.amd64.tar", "linux", "amd64")
1212
+ ]
1213
+ image_address for example: "myregistry.example.com/myapp"
1214
+ tag for example: "1.0.0"
1215
+ """
1216
+ arch_tags = []
1217
+
1218
+ for tar_path, os_name, arch in tar_files:
1219
+ arch_tag = f"{image_address}:{tag}-{os_name}-{arch}"
1220
+ arch_tags.append(arch_tag)
1221
+
1222
+ # Load tar → local image
1223
+ print(f"Loading {tar_path}...")
1224
+ result = self.__sc.run_program_argsasarray("docker",[ "load", "-i", tar_path], capture_output=True)
1225
+ # docker load outputs: "Loaded image: sha256:abc123..." or "Loaded image ID: ..."
1226
+ # we need the loaded image ID
1227
+ loaded_id = None
1228
+ for line in GeneralUtilities.string_to_lines(result[1]):
1229
+ if "Loaded image" in line:
1230
+ loaded_id = line.split(":", 1)[1].strip()
1231
+ break
1232
+
1233
+ if not loaded_id:
1234
+ raise RuntimeError(f"Could not determine loaded image from output: \"{result[1]}\"")
1235
+
1236
+ # Retag + push
1237
+ self.__sc.run_program_argsasarray("docker",[ "tag", loaded_id, arch_tag])
1238
+ self.__sc.run_program_argsasarray("docker",[ "push", arch_tag])
1239
+
1240
+ # Create multi-arch manifest
1241
+ final_tag = f"{image_address}:{tag}"
1242
+ self.__sc.run_program_argsasarray("docker", [ "buildx", "imagetools", "create", "--tag", final_tag] + arch_tags)
1243
+
1244
+
1245
+ @GeneralUtilities.check_arguments
1246
+ def platform_from_filename(self,filename: str) -> Platform:
1247
+ match = re.search(r'_([^_]+)\.tar', filename)
1248
+ if match:
1249
+ return GeneralUtilities.platform_from_dash_str(match.group(1))
1250
+ else:
1251
+ raise ValueError(f"Cannot extract platform from filename: \"{filename}\"")
1252
+
1184
1253
  def prepare_building_codeunits(self,repository_folder:str,use_cache:bool,generate_development_certificate:bool):
1185
1254
  if generate_development_certificate:
1186
1255
  self.ensure_certificate_authority_for_development_purposes_is_generated(repository_folder)
@@ -1207,10 +1276,14 @@ class TFCPS_Tools_General:
1207
1276
  self.__sc.run_program("docker", f"container rm -f {container_name}", throw_exception_if_exitcode_is_not_zero=False)
1208
1277
 
1209
1278
  @GeneralUtilities.check_arguments
1210
- def load_docker_image(self, oci_image_artifacts_folder:str) -> None:
1211
- image_filename = os.path.basename(self.__sc.find_file_by_extension(oci_image_artifacts_folder, "tar"))
1212
- self.__sc.log.log("Load docker-image...")
1213
- self.__sc.run_program("docker", f"load -i {image_filename}", oci_image_artifacts_folder)
1279
+ def load_docker_image(self, oci_image_artifacts_folder:str,platform:Platform) -> None:
1280
+ for file in GeneralUtilities.get_direct_files_of_folder(oci_image_artifacts_folder):
1281
+ if file.endswith(f"_{GeneralUtilities.platform_to_dash_str(platform)}.tar"):
1282
+ image_filename = file
1283
+ self.__sc.log.log("Load docker-image...")
1284
+ self.__sc.run_program("docker", f"load -i {image_filename}", oci_image_artifacts_folder)
1285
+ return
1286
+ raise ValueError(f"No docker-image found for platform {GeneralUtilities.platform_to_dash_str(platform)} in folder {oci_image_artifacts_folder}.")
1214
1287
 
1215
1288
  @GeneralUtilities.check_arguments
1216
1289
  def start_dockerfile_example(self, current_file: str,remove_old_container: bool, remove_volumes_folder: bool, env_file: str) -> None:
@@ -1225,7 +1298,7 @@ class TFCPS_Tools_General:
1225
1298
  self.__sc.log.log(f"Ensure container of {docker_compose_file} do not exist...")
1226
1299
  oci_image_artifacts_folder = GeneralUtilities.resolve_relative_path("../../../../Artifacts/BuildResult_OCIImage", folder_of_current_file)
1227
1300
  self.ensure_containers_are_not_running(container_names_to_remove)
1228
- self.load_docker_image(oci_image_artifacts_folder)
1301
+ self.load_docker_image(oci_image_artifacts_folder,GeneralUtilities.get_current_platform())
1229
1302
  example_name = os.path.basename(folder_of_current_file)
1230
1303
  codeunit_name = os.path.basename(GeneralUtilities.resolve_relative_path("../../../../..", folder_of_current_file))
1231
1304
  if remove_volumes_folder:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ScriptCollection
3
- Version: 4.2.58
3
+ Version: 4.2.60
4
4
  Summary: The ScriptCollection is the place for reusable scripts.
5
5
  Home-page: https://github.com/anionDev/ScriptCollection
6
6
  Author: Marius Göcke
@@ -1,7 +1,7 @@
1
1
  ScriptCollection/AnionBuildPlatform.py,sha256=K-PHarX802A0PU8uRu0GNcEZiXujFoXHACe-X9YJsAQ,11711
2
2
  ScriptCollection/CertificateUpdater.py,sha256=Pa6eyjQSx7IIvj4PQVMI0IwMs01KQrNSB7Qa-7lRfBs,9375
3
- ScriptCollection/Executables.py,sha256=O2apj_jYLmYy9_0iK3SFgez4EvfthIW1JsAmlGwp-8M,44534
4
- ScriptCollection/GeneralUtilities.py,sha256=J136V1Xb5-Q8VCKzms3oTo6teMjYPA4sMds8lD8R1PE,61183
3
+ ScriptCollection/Executables.py,sha256=qpo0g5peWdlK5uLIUCyLDB9c3JCk0ETbtmOJXZwuHh4,44510
4
+ ScriptCollection/GeneralUtilities.py,sha256=FEjJEfA3riVT7G4sL8bzKPb7w0SN8YjONNWMtiBo_j8,64882
5
5
  ScriptCollection/HTTPMaintenanceOverheadHelper.py,sha256=TToNtyO1XzsMbBsTBf3o0xgOK0v4Jf03qw2Z0xb2nCk,2007
6
6
  ScriptCollection/ProcessesRunner.py,sha256=o5raxIt3lknNPoPrjNzJ2bprRPJ3SnL0rrR7crraD7E,1523
7
7
  ScriptCollection/ProgramRunnerBase.py,sha256=4A2eQgSg_rRgQcgSi-LYtUlM-uSQEpS7qFWn0tWt4uo,2171
@@ -9,7 +9,7 @@ ScriptCollection/ProgramRunnerMock.py,sha256=uTu-aFle1W_oKjeQEmuPsFPQpvo0kRf2FrR
9
9
  ScriptCollection/ProgramRunnerPopen.py,sha256=BPY7-ZMIlqT7JOKz8qlB5c0laF2Js-ijzqk09GxZC48,3821
10
10
  ScriptCollection/ProgramRunnerSudo.py,sha256=_khC3xuTdrPoLluBJZWfldltmmuKltABJPcbjZSFW-4,4835
11
11
  ScriptCollection/SCLog.py,sha256=8TRy1LeYMsPOIuWUcnUNNbO5pd-cNBS-3cn-kdzP8FU,4768
12
- ScriptCollection/ScriptCollectionCore.py,sha256=c-wSuEa8_T_-GC-iE3fNO7Yr7gB92b4R1zAwQN2-QSM,174896
12
+ ScriptCollection/ScriptCollectionCore.py,sha256=k9Jxim56NM5dr6wAZrITk-bxZhKKxeXi1gb1zkfcDUo,176930
13
13
  ScriptCollection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  ScriptCollection/OCIImages/AbstractImageHandler.py,sha256=83qDMILwxhH9DbC0sb358Vu8PXEysmJJyap_6gECZqs,1627
15
15
  ScriptCollection/OCIImages/OCIImageManager.py,sha256=aBogkSXNDyi8NO11N-s03nuFJEv7PyJ-wjHuYYeZfvs,6662
@@ -30,9 +30,9 @@ ScriptCollection/TFCPS/TFCPS_Generic.py,sha256=O-0guM_LJCcZmPZJhMgTvXD2RXUJEBWWv
30
30
  ScriptCollection/TFCPS/TFCPS_MergeToMain.py,sha256=-Ev9D3bZDlUk2WFQhcmvzQ3FCS97OdsVUd0koAdmpZc,7474
31
31
  ScriptCollection/TFCPS/TFCPS_MergeToStable.py,sha256=Ajfy2pLajTuU6UpwItHt4C2a-gLF3gPc4z6BktL3Cio,22163
32
32
  ScriptCollection/TFCPS/TFCPS_PreBuildCodeunitsScript.py,sha256=f0Uq1cA_4LvmL72cal0crrbKF6PcxL13D9wBKuQ1YBw,2328
33
- ScriptCollection/TFCPS/TFCPS_Tools_General.py,sha256=WV67pHLrhD_qEjUqM-8tUpEdhQ4brwVentfIf7AMOP4,93942
33
+ ScriptCollection/TFCPS/TFCPS_Tools_General.py,sha256=RCwtEwuZUds-gyHa2qCitWZUgapM3-IZJKF9e5Zwxck,98000
34
34
  ScriptCollection/TFCPS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py,sha256=BPJkoy2KVgB_38cAk5qjM4s4FpJvOkTp467nrvANIIU,10606
35
+ ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py,sha256=xMuj-GoEwDnmUWbmdF536dNQL7jnTaw9aQ-gcDqUkgQ,11743
36
36
  ScriptCollection/TFCPS/Docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationBase.py,sha256=bT6Gd5pQpZCw4OQz6HWkPCSn5z__eUUEisABLDSxd0o,200
38
38
  ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationGenerate.py,sha256=QyjOfMY22JWCvKjMelHiDWbJiWqotOfebpJpgDUaoO4,237
@@ -45,10 +45,10 @@ ScriptCollection/TFCPS/Go/TFCPS_CodeUnitSpecific_Go.py,sha256=kyx26AnT1-LySFA46w
45
45
  ScriptCollection/TFCPS/Go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  ScriptCollection/TFCPS/NodeJS/TFCPS_CodeUnitSpecific_NodeJS.py,sha256=kL37qJNwH6E-CAJqcHbdnc0K0uGjHQ8XD1RXZOdcw1M,12850
47
47
  ScriptCollection/TFCPS/NodeJS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py,sha256=nLw_eSUd_56jjgfcAvtUyzecSZ14mYmNJl0iu-1YNVk,13496
48
+ ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py,sha256=KFKDdfV9DFHE5n7TI6m1Ra1Qw2JwL_JQjneBfqcRQ_w,13467
49
49
  ScriptCollection/TFCPS/Python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- scriptcollection-4.2.58.dist-info/METADATA,sha256=Lm4CyXwLiuq4Qr6srolxs5gr6CRuojWuzOeD9x0qonY,7690
51
- scriptcollection-4.2.58.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
52
- scriptcollection-4.2.58.dist-info/entry_points.txt,sha256=27XwAJEcaMEc1be0Ec1vKHCbiU4Ziu8jKL-SqsrYOIQ,4680
53
- scriptcollection-4.2.58.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
54
- scriptcollection-4.2.58.dist-info/RECORD,,
50
+ scriptcollection-4.2.60.dist-info/METADATA,sha256=dQYQcvTQgiDemUjezOsxBCyPOkzYt6DkBe7HSGQUxf4,7690
51
+ scriptcollection-4.2.60.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
52
+ scriptcollection-4.2.60.dist-info/entry_points.txt,sha256=27XwAJEcaMEc1be0Ec1vKHCbiU4Ziu8jKL-SqsrYOIQ,4680
53
+ scriptcollection-4.2.60.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
54
+ scriptcollection-4.2.60.dist-info/RECORD,,