ScriptCollection 3.5.119__py3-none-any.whl → 3.5.120__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.
@@ -24,7 +24,7 @@ class CertificateUpdater:
24
24
 
25
25
  def __init__(self, domains: list[str], email: str, current_file: str, arguments: list[str]):
26
26
  self.__sc = ScriptCollectionCore()
27
- self.maximal_age_of_certificates_in_days = 60
27
+ self.maximal_age_of_certificates_in_days = 15
28
28
  self.__domains = domains
29
29
  self.__email = email
30
30
  self.__current_folder = current_file
@@ -126,7 +126,7 @@ class CertificateUpdater:
126
126
  def __get_last_certificate_update_date(self) -> datetime:
127
127
  if os.path.exists(self.__last_update_timestamp_file):
128
128
  filecontent = GeneralUtilities.read_text_from_file(self.__last_update_timestamp_file)
129
- return GeneralUtilities.string_to_datetime(filecontent.replace("\r", "").replace("\n", ""))
129
+ return GeneralUtilities.string_to_datetime(filecontent.replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string))
130
130
  else:
131
131
  return datetime(year=1970, month=1, day=1)
132
132
 
@@ -142,5 +142,8 @@ class CertificateUpdater:
142
142
  args = parser.parse_args(self.__arguments)
143
143
  now = datetime.now()
144
144
  if (self.__get_last_certificate_update_date()+timedelta(days=self.maximal_age_of_certificates_in_days)) < now or args.force:
145
+ GeneralUtilities.write_message_to_stdout(f"Update certificates...")
145
146
  self.__update_certificates()
146
147
  self.__set_last_certificate_update_date(now)
148
+ else:
149
+ GeneralUtilities.write_message_to_stdout(f"Certificates are already up to date.")
@@ -38,6 +38,8 @@ class GeneralUtilities:
38
38
  __datetime_format: str = "%Y-%m-%dT%H:%M:%S"
39
39
  __date_format: str = "%Y-%m-%d"
40
40
 
41
+ empty_string: str = ""
42
+
41
43
  @staticmethod
42
44
  def get_modest_dark_url() -> str:
43
45
  return "https://aniondev.github.io/CDN/ScriptCollectionDesigns/ModestDark/Style.css"
@@ -46,6 +48,10 @@ class GeneralUtilities:
46
48
  def is_generic(t: typing.Type):
47
49
  return hasattr(t, "__origin__")
48
50
 
51
+ @staticmethod
52
+ def is_debugger_attached():
53
+ return sys.gettrace() is not None
54
+
49
55
  @staticmethod
50
56
  def check_arguments(function):
51
57
  def __check_function(*args, **named_args):
@@ -234,6 +240,32 @@ class GeneralUtilities:
234
240
  text = GeneralUtilities.replace_underscores_in_text(text, replacements)
235
241
  GeneralUtilities.write_text_to_file(file, text, encoding)
236
242
 
243
+ @staticmethod
244
+ def print_text(text: str, print_to_stdout: bool = True):
245
+ GeneralUtilities.__print_text_to_console(text, print_to_stdout)
246
+
247
+ @staticmethod
248
+ def print_text_in_green(text: str, print_to_stdout: bool = True):
249
+ GeneralUtilities.__print_text_to_console(f"\033[32m{text}\033[0m", print_to_stdout)
250
+
251
+ @staticmethod
252
+ def print_text_in_yellow(text: str, print_to_stdout: bool = True):
253
+ GeneralUtilities.__print_text_to_console(f"\033[33m{text}\033[0m", print_to_stdout)
254
+
255
+ @staticmethod
256
+ def print_text_in_red(text: str, print_to_stdout: bool = True):
257
+ GeneralUtilities.__print_text_to_console(f"\033[31m{text}\033[0m", print_to_stdout)
258
+
259
+ @staticmethod
260
+ def print_text_in_cyan(text: str, print_to_stdout: bool = True):
261
+ GeneralUtilities.__print_text_to_console(f"\033[36m{text}\033[0m", print_to_stdout)
262
+
263
+ @staticmethod
264
+ def __print_text_to_console(text: str, print_to_stdout: bool = True):
265
+ output = sys.stdout if print_to_stdout else sys.stderr
266
+ output.write(text)
267
+ output.flush()
268
+
237
269
  @staticmethod
238
270
  @check_arguments
239
271
  def reconfigure_standrd_input_and_outputs():
@@ -243,16 +275,29 @@ class GeneralUtilities:
243
275
 
244
276
  @staticmethod
245
277
  @check_arguments
246
- def write_message_to_stdout(message: str):
247
- for line in GeneralUtilities.string_to_lines(message):
248
- sys.stdout.write(GeneralUtilities.str_none_safe(line)+"\n")
278
+ def write_message_to_stdout_advanced(message: str, add_empty_lines: bool = True, adapt_lines: bool = True, append_linebreak: bool = True):
279
+ new_line_character: str = "\n" if append_linebreak else ""
280
+ for line in GeneralUtilities.string_to_lines(message, add_empty_lines, adapt_lines):
281
+ sys.stdout.write(GeneralUtilities.str_none_safe(line)+new_line_character)
249
282
  sys.stdout.flush()
250
283
 
284
+ @staticmethod
285
+ @check_arguments
286
+ def write_message_to_stdout(message: str):
287
+ GeneralUtilities.write_message_to_stdout_advanced(message, True, True, True)
288
+
289
+ @staticmethod
290
+ @check_arguments
291
+ def write_message_to_stderr_advanced(message: str, add_empty_lines: bool = True, adapt_lines: bool = True, append_linebreak: bool = True):
292
+ new_line_character: str = "\n" if append_linebreak else ""
293
+ for line in GeneralUtilities.string_to_lines(message, add_empty_lines, adapt_lines):
294
+ sys.stderr.write(GeneralUtilities.str_none_safe(line)+new_line_character)
295
+ sys.stderr.flush()
296
+
251
297
  @staticmethod
252
298
  @check_arguments
253
299
  def write_message_to_stderr(message: str):
254
- sys.stderr.write(GeneralUtilities.str_none_safe(message)+"\n")
255
- sys.stderr.flush()
300
+ GeneralUtilities.write_message_to_stderr_advanced(message, True, True, True)
256
301
 
257
302
  @staticmethod
258
303
  @check_arguments
@@ -260,7 +305,7 @@ class GeneralUtilities:
260
305
  if GeneralUtilities.string_has_content(os_error.filename2):
261
306
  secondpath = f" {os_error.filename2}"
262
307
  else:
263
- secondpath = ""
308
+ secondpath = GeneralUtilities.empty_string
264
309
  return f"Related path(s): {os_error.filename}{secondpath}"
265
310
 
266
311
  @staticmethod
@@ -315,7 +360,7 @@ class GeneralUtilities:
315
360
  return True
316
361
  type_of_argument = type(argument)
317
362
  if type_of_argument == str:
318
- return argument == ""
363
+ return argument == GeneralUtilities.empty_string
319
364
  else:
320
365
  raise ValueError(f"expected string-variable in argument of string_is_none_or_empty but the type was '{str(type_of_argument)}'")
321
366
 
@@ -325,7 +370,7 @@ class GeneralUtilities:
325
370
  if GeneralUtilities.string_is_none_or_empty(string):
326
371
  return True
327
372
  else:
328
- return string.strip() == ""
373
+ return string.strip() == GeneralUtilities.empty_string
329
374
 
330
375
  @staticmethod
331
376
  @check_arguments
@@ -378,7 +423,7 @@ class GeneralUtilities:
378
423
  if GeneralUtilities.file_ends_with_content(file):
379
424
  return "\n"
380
425
  else:
381
- return ""
426
+ return GeneralUtilities.empty_string
382
427
 
383
428
  @staticmethod
384
429
  @check_arguments
@@ -861,7 +906,7 @@ class GeneralUtilities:
861
906
  result = list()
862
907
  if list_as_string is not None:
863
908
  list_as_string = list_as_string.strip()
864
- if list_as_string == "":
909
+ if list_as_string == GeneralUtilities.empty_string:
865
910
  pass
866
911
  elif separator in list_as_string:
867
912
  for item in list_as_string.split(separator):
@@ -922,7 +967,7 @@ class GeneralUtilities:
922
967
  if positive:
923
968
  return "✅"
924
969
  else:
925
- return ""
970
+ return GeneralUtilities.empty_string
926
971
 
927
972
  @staticmethod
928
973
  @check_arguments
@@ -0,0 +1,477 @@
1
+ from abc import abstractmethod
2
+ from enum import Enum
3
+ import json
4
+ import re
5
+ from urllib.parse import quote
6
+ import yaml
7
+ import requests
8
+ from packaging import version as ve
9
+ from packaging.version import Version
10
+ from .GeneralUtilities import GeneralUtilities
11
+
12
+
13
+ class VersionEcholon(Enum):
14
+ Patch = 0
15
+ LatestPatchOrLatestMinor = 1
16
+ LatestPatchOrLatestMinorOrNextMajor = 2
17
+ Newest = 3
18
+
19
+
20
+ class ImageUpdaterHelper:
21
+
22
+ @staticmethod
23
+ @GeneralUtilities.check_arguments
24
+ def _internal_filter_for_major_and_minor_versions(versions: list[Version], major: int, minor: int) -> Version:
25
+ return [v for v in versions if v.major == major and v.minor == minor]
26
+
27
+ @staticmethod
28
+ @GeneralUtilities.check_arguments
29
+ def _internal_filter_for_major_versions(versions: list[Version], major: int) -> Version:
30
+ return [v for v in versions if v.major == major]
31
+
32
+ @staticmethod
33
+ @GeneralUtilities.check_arguments
34
+ def _internal_get_latest_patch_version(newer_versions: list[Version], current_version: Version) -> Version:
35
+ candidates = ImageUpdaterHelper._internal_filter_for_major_and_minor_versions(newer_versions, current_version.major, current_version.minor)
36
+ if len(candidates) == 0:
37
+ return current_version
38
+ result = ImageUpdaterHelper.get_latest_version(candidates)
39
+ return result
40
+
41
+ @staticmethod
42
+ @GeneralUtilities.check_arguments
43
+ def _internal_get_latest_patch_or_latest_minor_version(newer_versions: list[Version], current_version: Version) -> Version:
44
+ candidates = ImageUpdaterHelper._internal_filter_for_major_versions(newer_versions, current_version.major)
45
+ if len(candidates) == 0:
46
+ return current_version
47
+ result = ImageUpdaterHelper.get_latest_version(candidates)
48
+ return result
49
+
50
+ @staticmethod
51
+ @GeneralUtilities.check_arguments
52
+ def _internal_get_latest_patch_or_latest_minor_or_next_major_version(newer_versions: list[Version], current_version: Version) -> Version:
53
+ candidates = ImageUpdaterHelper._internal_filter_for_major_versions(newer_versions, current_version.major+1)
54
+ if 0 < len(candidates):
55
+ result = ImageUpdaterHelper.get_latest_version(candidates)
56
+ return result
57
+ else:
58
+ candidates = ImageUpdaterHelper._internal_filter_for_major_versions(newer_versions, current_version.major)
59
+ if len(candidates) == 0:
60
+ return current_version
61
+ result = ImageUpdaterHelper.get_latest_version(candidates)
62
+ return result
63
+
64
+ @staticmethod
65
+ @GeneralUtilities.check_arguments
66
+ def filter_considering_echolog(newer_versions: list[Version], current_version: Version, version_echolon: VersionEcholon) -> Version:
67
+ if version_echolon == VersionEcholon.Patch:
68
+ return ImageUpdaterHelper._internal_get_latest_patch_version(newer_versions, current_version)
69
+ elif version_echolon == VersionEcholon.LatestPatchOrLatestMinor:
70
+ return ImageUpdaterHelper._internal_get_latest_patch_or_latest_minor_version(newer_versions, current_version)
71
+ elif version_echolon == VersionEcholon.LatestPatchOrLatestMinorOrNextMajor:
72
+ return ImageUpdaterHelper._internal_get_latest_patch_or_latest_minor_or_next_major_version(newer_versions, current_version)
73
+ elif version_echolon == VersionEcholon.Newest:
74
+ return ImageUpdaterHelper.get_latest_version(newer_versions)
75
+ else:
76
+ raise ValueError(f"Unknown version-echolon")
77
+
78
+ @staticmethod
79
+ @GeneralUtilities.check_arguments
80
+ def get_latest_version(versions: list[Version]) -> Version:
81
+ result = max(versions)
82
+ return result
83
+
84
+ @staticmethod
85
+ @GeneralUtilities.check_arguments
86
+ def get_latest_version_from_versiontrings(version_strings: list[str]) -> str:
87
+ parsed = [ve.parse(v) for v in version_strings]
88
+ result = max(parsed)
89
+ return str(result)
90
+
91
+ @staticmethod
92
+ @GeneralUtilities.check_arguments
93
+ def filter_for_newer_versions(comparison_version: Version, versions_to_filter: list[Version]) -> list[Version]:
94
+ result = [v for v in versions_to_filter if comparison_version < v]
95
+ return result
96
+
97
+ @staticmethod
98
+ @GeneralUtilities.check_arguments
99
+ def get_versions_in_docker_hub(image: str, search_string: str, filter_regex: str, maximal_amount_of_items_to_load: int = 250) -> list[Version]:
100
+ if "/" not in image:
101
+ image = f"library/{image}"
102
+ response = requests.get(f"https://hub.docker.com/v2/repositories/{quote(image)}/tags?name={quote(search_string)}&ordering=last_updated&page=1&page_size={str(maximal_amount_of_items_to_load)}", timeout=20, headers={'Cache-Control': 'no-cache'})
103
+ if response.status_code != 200:
104
+ raise ValueError(f"Failed to fetch data from Docker Hub: {response.status_code}")
105
+ response_text = response.text
106
+ data = json.loads(response_text)
107
+ tags: list[str] = [tag["name"] for tag in data["results"] if re.match(filter_regex, tag["name"])]
108
+ versions = [tag.split("-")[0] for tag in tags]
109
+ result = [ve.parse(v) for v in versions]
110
+ return result
111
+
112
+
113
+ class ConcreteImageUpdater:
114
+
115
+ @abstractmethod
116
+ @GeneralUtilities.check_arguments
117
+ def version_to_tag(self, version: Version) -> str:
118
+ raise NotImplementedError
119
+
120
+ @abstractmethod
121
+ @GeneralUtilities.check_arguments
122
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
123
+ raise NotImplementedError
124
+
125
+ @abstractmethod
126
+ @GeneralUtilities.check_arguments
127
+ def get_supported_images(self) -> list[str]:
128
+ raise NotImplementedError
129
+
130
+ @abstractmethod
131
+ @GeneralUtilities.check_arguments
132
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
133
+ raise NotImplementedError
134
+
135
+
136
+ class ConcreteImageUpdaterForNginx(ConcreteImageUpdater):
137
+
138
+ @GeneralUtilities.check_arguments
139
+ def version_to_tag(self, version: Version) -> str:
140
+ raise NotImplementedError
141
+
142
+ @GeneralUtilities.check_arguments
143
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
144
+ raise NotImplementedError
145
+
146
+ @GeneralUtilities.check_arguments
147
+ def get_supported_images(self) -> list[str]:
148
+ return ["nginx"]
149
+
150
+ @GeneralUtilities.check_arguments
151
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
152
+ raise NotImplementedError
153
+
154
+
155
+ class ConcreteImageUpdaterForGitLab(ConcreteImageUpdater):
156
+
157
+ @GeneralUtilities.check_arguments
158
+ def version_to_tag(self, version: Version) -> str:
159
+ raise NotImplementedError
160
+
161
+ @GeneralUtilities.check_arguments
162
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
163
+ raise NotImplementedError
164
+
165
+ @GeneralUtilities.check_arguments
166
+ def get_supported_images(self) -> list[str]:
167
+ return ["gitlab/gitlab-ce", "gitlab/gitlab-ee"]
168
+
169
+ @GeneralUtilities.check_arguments
170
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
171
+ raise NotImplementedError
172
+
173
+
174
+ class ConcreteImageUpdaterForRegistry(ConcreteImageUpdater):
175
+
176
+ @GeneralUtilities.check_arguments
177
+ def version_to_tag(self, version: Version) -> str:
178
+ raise NotImplementedError
179
+
180
+ @GeneralUtilities.check_arguments
181
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
182
+ raise NotImplementedError
183
+
184
+ @abstractmethod
185
+ @GeneralUtilities.check_arguments
186
+ def get_supported_images(self) -> list[str]:
187
+ return [] # TODO
188
+
189
+ @GeneralUtilities.check_arguments
190
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
191
+ raise NotImplementedError
192
+
193
+
194
+ class ConcreteImageUpdaterForPrometheus(ConcreteImageUpdater):
195
+
196
+ @GeneralUtilities.check_arguments
197
+ def version_to_tag(self, version: Version) -> str:
198
+ raise NotImplementedError
199
+
200
+ @GeneralUtilities.check_arguments
201
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
202
+ raise NotImplementedError
203
+
204
+ @GeneralUtilities.check_arguments
205
+ def get_supported_images(self) -> list[str]:
206
+ return [] # TODO
207
+
208
+ @GeneralUtilities.check_arguments
209
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
210
+ raise NotImplementedError
211
+
212
+
213
+ class ConcreteImageUpdaterForPrometheusBlackboxExporter(ConcreteImageUpdater):
214
+
215
+ @GeneralUtilities.check_arguments
216
+ def version_to_tag(self, version: Version) -> str:
217
+ raise NotImplementedError
218
+
219
+ @GeneralUtilities.check_arguments
220
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
221
+ raise NotImplementedError
222
+
223
+ @GeneralUtilities.check_arguments
224
+ def get_supported_images(self) -> list[str]:
225
+ return [] # TODO
226
+
227
+ @GeneralUtilities.check_arguments
228
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
229
+ raise NotImplementedError
230
+
231
+
232
+ class ConcreteImageUpdaterForPrometheusNginxExporter(ConcreteImageUpdater):
233
+
234
+ @GeneralUtilities.check_arguments
235
+ def version_to_tag(self, version: Version) -> str:
236
+ raise NotImplementedError
237
+
238
+ @GeneralUtilities.check_arguments
239
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
240
+ raise NotImplementedError
241
+
242
+ @GeneralUtilities.check_arguments
243
+ def get_supported_images(self) -> list[str]:
244
+ return [] # TODO
245
+
246
+ @GeneralUtilities.check_arguments
247
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
248
+ raise NotImplementedError
249
+
250
+
251
+ class ConcreteImageUpdaterForPrometheusNodeExporter(ConcreteImageUpdater):
252
+
253
+ @GeneralUtilities.check_arguments
254
+ def version_to_tag(self, version: Version) -> str:
255
+ raise NotImplementedError
256
+
257
+ @GeneralUtilities.check_arguments
258
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
259
+ raise NotImplementedError
260
+
261
+ @GeneralUtilities.check_arguments
262
+ def get_supported_images(self) -> list[str]:
263
+ return [] # TODO
264
+
265
+ @GeneralUtilities.check_arguments
266
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
267
+ raise NotImplementedError
268
+
269
+
270
+ class ConcreteImageUpdaterForKeycloak(ConcreteImageUpdater):
271
+
272
+ @GeneralUtilities.check_arguments
273
+ def version_to_tag(self, version: Version) -> str:
274
+ raise NotImplementedError
275
+
276
+ @GeneralUtilities.check_arguments
277
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
278
+ raise NotImplementedError
279
+
280
+ @GeneralUtilities.check_arguments
281
+ def get_supported_images(self) -> list[str]:
282
+ return [] # TODO
283
+
284
+ @GeneralUtilities.check_arguments
285
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
286
+ raise NotImplementedError
287
+
288
+
289
+ class ConcreteImageUpdaterForMariaDB(ConcreteImageUpdater):
290
+
291
+ @GeneralUtilities.check_arguments
292
+ def version_to_tag(self, version: Version) -> str:
293
+ return f"{version.major}.{version.minor}.{version.micro}"
294
+
295
+ @GeneralUtilities.check_arguments
296
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
297
+ versions = ImageUpdaterHelper.get_versions_in_docker_hub(image, ".", "^\\d+\\.\\d+\\.\\d+$", 999)
298
+ newer_versions = ImageUpdaterHelper.filter_for_newer_versions(current_version, versions)
299
+ result = ImageUpdaterHelper.filter_considering_echolog(newer_versions, current_version, version_echolon)
300
+ return result
301
+
302
+ @GeneralUtilities.check_arguments
303
+ def get_supported_images(self) -> list[str]:
304
+ return ["mariadb"]
305
+
306
+ @GeneralUtilities.check_arguments
307
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
308
+ return ve.parse(tag)
309
+
310
+
311
+ class ConcreteImageUpdaterForPostgreSQL(ConcreteImageUpdater):
312
+
313
+ @GeneralUtilities.check_arguments
314
+ def version_to_tag(self, version: Version) -> str:
315
+ raise NotImplementedError
316
+
317
+ @GeneralUtilities.check_arguments
318
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
319
+ raise NotImplementedError
320
+
321
+ @GeneralUtilities.check_arguments
322
+ def get_supported_images(self) -> list[str]:
323
+ return [] # TODO
324
+
325
+ @GeneralUtilities.check_arguments
326
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
327
+ raise NotImplementedError
328
+
329
+
330
+ class ConcreteImageUpdaterForAdminer(ConcreteImageUpdater):
331
+
332
+ @GeneralUtilities.check_arguments
333
+ def version_to_tag(self, version: Version) -> str:
334
+ return f"{version.major}.{version.minor}.{version.micro}"
335
+
336
+ @GeneralUtilities.check_arguments
337
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> Version:
338
+ versions = ImageUpdaterHelper.get_versions_in_docker_hub(image, ".", "^\\d+\\.\\d+\\.\\d+$", 999)
339
+ newer_versions = ImageUpdaterHelper.filter_for_newer_versions(current_version, versions)
340
+ result = ImageUpdaterHelper.filter_considering_echolog(newer_versions, current_version, version_echolon)
341
+ return result
342
+
343
+ @GeneralUtilities.check_arguments
344
+ def get_supported_images(self) -> list[str]:
345
+ return ["adminer"]
346
+
347
+ @GeneralUtilities.check_arguments
348
+ def get_version_from_tag(self, image: str, tag: str) -> Version:
349
+ return ve.parse(tag)
350
+
351
+
352
+ class ImageUpdater:
353
+
354
+ updater: list[ConcreteImageUpdater] = None
355
+
356
+ def __init__(self):
357
+ self.updater = list[ConcreteImageUpdater]()
358
+
359
+ def add_default_mapper(self) -> None:
360
+ self.updater.append(ConcreteImageUpdaterForNginx())
361
+ self.updater.append(ConcreteImageUpdaterForGitLab())
362
+ self.updater.append(ConcreteImageUpdaterForRegistry())
363
+ self.updater.append(ConcreteImageUpdaterForPrometheus())
364
+ self.updater.append(ConcreteImageUpdaterForPrometheusBlackboxExporter())
365
+ self.updater.append(ConcreteImageUpdaterForPrometheusNginxExporter())
366
+ self.updater.append(ConcreteImageUpdaterForPrometheusNodeExporter())
367
+ self.updater.append(ConcreteImageUpdaterForKeycloak())
368
+ self.updater.append(ConcreteImageUpdaterForMariaDB())
369
+ self.updater.append(ConcreteImageUpdaterForPostgreSQL())
370
+ self.updater.append(ConcreteImageUpdaterForAdminer())
371
+
372
+ @GeneralUtilities.check_arguments
373
+ def check_service_for_newest_version(self, dockercompose_file: str, service_name: str) -> bool:
374
+ imagename, existing_tag, existing_version = self.get_current_version_of_service_from_docker_compose_file(dockercompose_file, service_name) # pylint:disable=unused-variable
375
+ newest_version, newest_tag = self.get_latest_version_of_image(imagename, VersionEcholon.Newest, existing_version) # pylint:disable=unused-variable
376
+ if existing_version < newest_version:
377
+ GeneralUtilities.write_message_to_stdout(f"Service {service_name} with image {imagename} uses tag {existing_version}. The newest available version of this image is {newest_version}.")
378
+ return True
379
+ else:
380
+ return False
381
+
382
+ @GeneralUtilities.check_arguments
383
+ def check_for_newest_version(self, dockercompose_file: str, excluded_services: list[str] = []) -> bool:
384
+ all_services = self.get_services_from_docker_compose_file(dockercompose_file)
385
+ services_to_check = [service for service in all_services if service not in all_services]
386
+ newer_version_available: bool = False
387
+ for service_to_check in services_to_check:
388
+ if self.check_service_for_newest_version(dockercompose_file, service_to_check):
389
+ newer_version_available = True
390
+ return newer_version_available
391
+
392
+ @GeneralUtilities.check_arguments
393
+ def update_all_services_in_docker_compose_file(self, dockercompose_file: str, version_echolon: VersionEcholon, except_services: list[str] = []):
394
+ all_services = self.get_services_from_docker_compose_file(dockercompose_file)
395
+ services_to_update = [service for service in all_services if service not in except_services]
396
+ self.update_services_in_docker_compose_file(dockercompose_file, services_to_update, version_echolon)
397
+
398
+ @GeneralUtilities.check_arguments
399
+ def update_services_in_docker_compose_file(self, dockercompose_file: str, service_names: list[str], version_echolon: VersionEcholon):
400
+ for service_name in service_names:
401
+ if self.service_has_image_information(dockercompose_file, service_name):
402
+ self.update_service_in_docker_compose_file(dockercompose_file, service_name, version_echolon)
403
+
404
+ @GeneralUtilities.check_arguments
405
+ def service_has_image_information(self, dockercompose_file: str, service_name: str) -> bool:
406
+ with open(dockercompose_file, 'r', encoding="utf-8") as file:
407
+ compose_data = yaml.safe_load(file)
408
+ service = compose_data.get('services', {}).get(service_name, {})
409
+ image = service.get('image', None)
410
+ return image is not None
411
+
412
+ @GeneralUtilities.check_arguments
413
+ def update_service_in_docker_compose_file(self, dockercompose_file: str, service_name: str, version_echolon: VersionEcholon):
414
+ imagename, existing_tag, existing_version = self.get_current_version_of_service_from_docker_compose_file(dockercompose_file, service_name) # pylint:disable=unused-variable
415
+ result = self.get_latest_version_of_image(imagename, version_echolon, existing_version)
416
+ newest_version = result[0]
417
+ newest_tag = result[1]
418
+ if existing_version < newest_version:
419
+
420
+ with open(dockercompose_file, 'r', encoding="utf-8") as f:
421
+ compose_data = yaml.safe_load(f)
422
+
423
+ services = compose_data.get("services", {})
424
+ if service_name not in services:
425
+ raise ValueError(f"Service '{service_name}' not found.")
426
+
427
+ image = services[service_name].get("image")
428
+ if not image:
429
+ raise ValueError(f"Service '{service_name}' does not have an image-field.")
430
+
431
+ imagename = image.split(":")[0]
432
+ services[service_name]["image"] = imagename+":"+newest_tag
433
+
434
+ with open(dockercompose_file, 'w', encoding="utf-8") as f:
435
+ yaml.dump(compose_data, f, default_flow_style=False)
436
+
437
+ @GeneralUtilities.check_arguments
438
+ def get_current_version_of_service_from_docker_compose_file(self, dockercompose_file: str, service_name: str) -> tuple[str, str, Version]: # returns (image,existing_tag,existing_version)
439
+ with open(dockercompose_file, 'r', encoding="utf-8") as file:
440
+ compose_data = yaml.safe_load(file)
441
+ service = compose_data.get('services', {}).get(service_name, {})
442
+ image = str(service.get('image', None))
443
+ if image:
444
+ if ':' in image:
445
+ name, tag = image.rsplit(':', 1)
446
+ else:
447
+ name, tag = image, 'latest'
448
+ return name, tag, self.get_docker_version_from_tag(name, tag)
449
+ else:
450
+ raise ValueError(f"Service '{service_name}' in '{dockercompose_file}'")
451
+
452
+ @GeneralUtilities.check_arguments
453
+ def __get_updater_for_image(self, image: str) -> str:
454
+ for updater in self.updater:
455
+ if image in updater.get_supported_images():
456
+ return updater
457
+ raise ValueError(f"No updater available for image '{image}'")
458
+
459
+ @GeneralUtilities.check_arguments
460
+ def get_docker_version_from_tag(self, image: str, tag: str) -> Version:
461
+ updater: ImageUpdater = self.__get_updater_for_image(image)
462
+ return updater.get_version_from_tag(image, tag)
463
+
464
+ @GeneralUtilities.check_arguments
465
+ def get_latest_version_of_image(self, image: str, version_echolon: VersionEcholon, current_version: Version) -> tuple[Version, str]:
466
+ updater: ImageUpdater = self.__get_updater_for_image(image)
467
+ newest_version: Version = updater.get_latest_version_of_image(image, version_echolon, current_version)
468
+ newest_tag: str = updater.version_to_tag(newest_version)
469
+ return (newest_version, newest_tag)
470
+
471
+ @GeneralUtilities.check_arguments
472
+ def get_services_from_docker_compose_file(self, dockercompose_file: str) -> list[str]:
473
+ with open(dockercompose_file, 'r', encoding="utf-8") as f:
474
+ compose_data = yaml.safe_load(f)
475
+ services = compose_data.get('services', {})
476
+ result = list(services.keys())
477
+ return result
ScriptCollection/SCLog.py CHANGED
@@ -30,41 +30,54 @@ class SCLog:
30
30
 
31
31
  @GeneralUtilities.check_arguments
32
32
  def log(self, message: str, loglevel: LogLevel = None):
33
+ for line in GeneralUtilities.string_to_lines(message, True, False):
34
+ self.__log_line(line, loglevel)
35
+
36
+ @GeneralUtilities.check_arguments
37
+ def __log_line(self, message: str, loglevel: LogLevel = None):
33
38
  if loglevel is None:
34
39
  loglevel = LogLevel.Information
35
40
 
36
41
  if int(loglevel) > int(self.loglevel):
37
42
  return
38
43
 
44
+ part1: str = ""
45
+ part2: str = ""
46
+ part3: str = message
47
+
39
48
  if loglevel == LogLevel.Warning:
40
- message = f"Warning: {message}"
49
+ part3 = f"Warning: {message}"
41
50
  if loglevel == LogLevel.Debug:
42
- message = f"Debug: {message}"
51
+ part3 = f"Debug: {message}"
43
52
  if self.add_overhead:
44
- if loglevel == LogLevel.Error:
45
- message = f"[Error] {message}"
53
+ part1 = f"[{GeneralUtilities.datetime_to_string_for_logfile_entry(datetime.now())}] ["
54
+ if loglevel == LogLevel.Information:
55
+ part2 = f"Information"
56
+ elif loglevel == LogLevel.Error:
57
+ part2 = f"Error"
46
58
  elif loglevel == LogLevel.Warning:
47
- message = f"[Warning] {message}"
59
+ part2 = f"Warning"
48
60
  elif loglevel == LogLevel.Debug:
49
- message = f"[Debug] {message}"
50
- elif loglevel == LogLevel.Information:
51
- message = f"[Information] {message}"
61
+ part2 = f"Debug"
52
62
  else:
53
63
  raise ValueError("Unknown loglevel.")
64
+ part3 = f"] {message}"
54
65
 
55
- message = f"[{GeneralUtilities.datetime_to_string_for_logfile_entry(datetime.now())}] {message}"
56
-
57
- if loglevel == LogLevel.Error:
58
- GeneralUtilities.write_message_to_stderr(message)
66
+ print_to_std_out: bool = loglevel in (LogLevel.Debug, LogLevel.Information)
67
+ GeneralUtilities.print_text(part1, print_to_std_out)
68
+ # if the control-characters for colors cause problems then maybe it can be checked with sys.stdout.isatty() if colors should be printed
69
+ if loglevel == LogLevel.Information:
70
+ GeneralUtilities.print_text_in_green(part2, print_to_std_out)
71
+ elif loglevel == LogLevel.Error:
72
+ GeneralUtilities.print_text_in_red(part2, print_to_std_out)
59
73
  elif loglevel == LogLevel.Warning:
60
- GeneralUtilities.write_message_to_stderr(message)
74
+ GeneralUtilities.print_text_in_yellow(part2, print_to_std_out)
61
75
  elif loglevel == LogLevel.Debug:
62
- GeneralUtilities.write_message_to_stdout(message)
63
- elif loglevel == LogLevel.Information:
64
- GeneralUtilities.write_message_to_stdout(message)
76
+ GeneralUtilities.print_text_in_cyan(part2, print_to_std_out)
65
77
  else:
66
78
  raise ValueError("Unknown loglevel.")
79
+ GeneralUtilities.print_text(part3+"\n", print_to_std_out)
67
80
 
68
81
  if self.log_file is not None:
69
82
  GeneralUtilities.ensure_file_exists(self.log_file)
70
- GeneralUtilities.append_line_to_file(self.log_file, message)
83
+ GeneralUtilities.append_line_to_file(self.log_file, part1+part2+part3)
@@ -28,12 +28,12 @@ import qrcode
28
28
  import pycdlib
29
29
  import send2trash
30
30
  from pypdf import PdfReader, PdfWriter
31
- from .GeneralUtilities import GeneralUtilities, VersionEcholon
31
+ from .GeneralUtilities import GeneralUtilities
32
32
  from .ProgramRunnerBase import ProgramRunnerBase
33
33
  from .ProgramRunnerPopen import ProgramRunnerPopen
34
34
  from .ProgramRunnerEpew import ProgramRunnerEpew, CustomEpewArgument
35
35
 
36
- version = "3.5.119"
36
+ version = "3.5.120"
37
37
  __version__ = version
38
38
 
39
39
 
@@ -63,7 +63,7 @@ class ScriptCollectionCore:
63
63
  errors = list()
64
64
  filename = os.path.relpath(file, working_directory)
65
65
  if treat_warnings_as_errors:
66
- errorsonly_argument = ""
66
+ errorsonly_argument = GeneralUtilities.empty_string
67
67
  else:
68
68
  errorsonly_argument = " --errors-only"
69
69
  (exit_code, stdout, stderr, _) = self.run_program("pylint", filename + errorsonly_argument, working_directory, throw_exception_if_exitcode_is_not_zero=False)
@@ -167,14 +167,14 @@ class ScriptCollectionCore:
167
167
  @GeneralUtilities.check_arguments
168
168
  def get_parent_commit_ids_of_commit(self, repository_folder: str, commit_id: str) -> str:
169
169
  self.assert_is_git_repository(repository_folder)
170
- return self.run_program("git", f'log --pretty=%P -n 1 "{commit_id}"', repository_folder, throw_exception_if_exitcode_is_not_zero=True)[1].replace("\r", "").replace("\n", "").split(" ")
170
+ return self.run_program("git", f'log --pretty=%P -n 1 "{commit_id}"', repository_folder, throw_exception_if_exitcode_is_not_zero=True)[1].replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string).split(" ")
171
171
 
172
172
  @GeneralUtilities.check_arguments
173
173
  def get_all_authors_and_committers_of_repository(self, repository_folder: str, subfolder: str = None, verbosity: int = 1) -> list[tuple[str, str]]:
174
174
  self.assert_is_git_repository(repository_folder)
175
175
  space_character = "_"
176
176
  if subfolder is None:
177
- subfolder_argument = ""
177
+ subfolder_argument = GeneralUtilities.empty_string
178
178
  else:
179
179
  subfolder_argument = f" -- {subfolder}"
180
180
  log_result = self.run_program("git", f'log --pretty=%aN{space_character}%aE%n%cN{space_character}%cE HEAD{subfolder_argument}', repository_folder, verbosity=0)
@@ -194,7 +194,7 @@ class ScriptCollectionCore:
194
194
  self.assert_is_git_repository(repository_folder)
195
195
  since_as_string = self.__datetime_to_string_for_git(since)
196
196
  until_as_string = self.__datetime_to_string_for_git(until)
197
- result = filter(lambda line: not GeneralUtilities.string_is_none_or_whitespace(line), self.run_program("git", f'log --since "{since_as_string}" --until "{until_as_string}" --pretty=format:"%H" --no-patch', repository_folder, throw_exception_if_exitcode_is_not_zero=True)[1].split("\n").replace("\r", ""))
197
+ result = filter(lambda line: not GeneralUtilities.string_is_none_or_whitespace(line), self.run_program("git", f'log --since "{since_as_string}" --until "{until_as_string}" --pretty=format:"%H" --no-patch', repository_folder, throw_exception_if_exitcode_is_not_zero=True)[1].split("\n").replace("\r", GeneralUtilities.empty_string))
198
198
  if ignore_commits_which_are_not_in_history_of_head:
199
199
  result = [commit_id for commit_id in result if self.git_commit_is_ancestor(repository_folder, commit_id)]
200
200
  return result
@@ -557,19 +557,19 @@ class ScriptCollectionCore:
557
557
  def git_get_current_branch_name(self, repository: str) -> str:
558
558
  self.assert_is_git_repository(repository)
559
559
  result = self.run_program_argsasarray("git", ["rev-parse", "--abbrev-ref", "HEAD"], repository, throw_exception_if_exitcode_is_not_zero=True, verbosity=0)
560
- return result[1].replace("\r", "").replace("\n", "")
560
+ return result[1].replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string)
561
561
 
562
562
  @GeneralUtilities.check_arguments
563
563
  def git_get_commitid_of_tag(self, repository: str, tag: str) -> str:
564
564
  self.assert_is_git_repository(repository)
565
565
  stdout = self.run_program_argsasarray("git", ["rev-list", "-n", "1", tag], repository, verbosity=0)
566
- result = stdout[1].replace("\r", "").replace("\n", "")
566
+ result = stdout[1].replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string)
567
567
  return result
568
568
 
569
569
  @GeneralUtilities.check_arguments
570
570
  def git_get_tags(self, repository: str) -> list[str]:
571
571
  self.assert_is_git_repository(repository)
572
- tags = [line.replace("\r", "") for line in self.run_program_argsasarray(
572
+ tags = [line.replace("\r", GeneralUtilities.empty_string) for line in self.run_program_argsasarray(
573
573
  "git", ["tag"], repository)[1].split("\n") if len(line) > 0]
574
574
  return tags
575
575
 
@@ -604,14 +604,14 @@ class ScriptCollectionCore:
604
604
  def get_latest_git_tag(self, repository_folder: str) -> str:
605
605
  self.assert_is_git_repository(repository_folder)
606
606
  result = self.run_program_argsasarray("git", ["describe", "--tags", "--abbrev=0"], repository_folder, verbosity=0)
607
- result = result[1].replace("\r", "").replace("\n", "")
607
+ result = result[1].replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string)
608
608
  return result
609
609
 
610
610
  @GeneralUtilities.check_arguments
611
611
  def get_staged_or_committed_git_ignored_files(self, repository_folder: str) -> list[str]:
612
612
  self.assert_is_git_repository(repository_folder)
613
613
  temp_result = self.run_program_argsasarray("git", ["ls-files", "-i", "-c", "--exclude-standard"], repository_folder, verbosity=0)
614
- temp_result = temp_result[1].replace("\r", "")
614
+ temp_result = temp_result[1].replace("\r", GeneralUtilities.empty_string)
615
615
  result = [line for line in temp_result.split("\n") if len(line) > 0]
616
616
  return result
617
617
 
@@ -1243,7 +1243,7 @@ class ScriptCollectionCore:
1243
1243
  def __adjust_folder_name(self, folder: str) -> str:
1244
1244
  result = os.path.dirname(folder).replace("\\", "/")
1245
1245
  if result == "/":
1246
- return ""
1246
+ return GeneralUtilities.empty_string
1247
1247
  else:
1248
1248
  return result
1249
1249
 
@@ -1449,7 +1449,7 @@ class ScriptCollectionCore:
1449
1449
  GeneralUtilities.assert_condition(not GeneralUtilities.string_is_none_or_whitespace(ls_result[1]), f"'ls -ld' of '{file_or_folder}' had an empty output. StdErr: '{ls_result[2]}'")
1450
1450
  GeneralUtilities.write_message_to_stdout(ls_result[1])
1451
1451
  output = ls_result[1]
1452
- result = output.replace("\n", "")
1452
+ result = output.replace("\n", GeneralUtilities.empty_string)
1453
1453
  result = ' '.join(result.split()) # reduce multiple whitespaces to one
1454
1454
  return result
1455
1455
 
@@ -1538,7 +1538,7 @@ class ScriptCollectionCore:
1538
1538
  try:
1539
1539
  while not q_stdout.empty():
1540
1540
  out_line: str = q_stdout.get_nowait()
1541
- out_line = out_line.replace("\r", "").replace("\n", "")
1541
+ out_line = out_line.replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string)
1542
1542
  if GeneralUtilities.string_has_content(out_line):
1543
1543
  stdout_result.append(out_line)
1544
1544
  reading_stdout_last_time_resulted_in_exception = False
@@ -1552,7 +1552,7 @@ class ScriptCollectionCore:
1552
1552
  try:
1553
1553
  while not q_stderr.empty():
1554
1554
  err_line: str = q_stderr.get_nowait()
1555
- err_line = err_line.replace("\r", "").replace("\n", "")
1555
+ err_line = err_line.replace("\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string)
1556
1556
  if GeneralUtilities.string_has_content(err_line):
1557
1557
  stderr_result.append(err_line)
1558
1558
  reading_stderr_last_time_resulted_in_exception = False
@@ -1600,8 +1600,8 @@ class ScriptCollectionCore:
1600
1600
  GeneralUtilities.write_message_to_stdout(f"Run '{info_for_log}'.")
1601
1601
 
1602
1602
  exit_code: int = None
1603
- stdout: str = ""
1604
- stderr: str = ""
1603
+ stdout: str = GeneralUtilities.empty_string
1604
+ stderr: str = GeneralUtilities.empty_string
1605
1605
  pid: int = None
1606
1606
 
1607
1607
  with self.__run_program_argsasarray_async_helper(program, arguments_as_array, working_directory, verbosity, print_errors_as_information, log_file, timeoutInSeconds, addLogOverhead, title, log_namespace, arguments_for_log, custom_argument, interactive) as process:
@@ -1971,10 +1971,10 @@ DNS = {domain}
1971
1971
  csproj_filename = os.path.basename(csproj_file)
1972
1972
  GeneralUtilities.write_message_to_stdout(f"Check for updates in {csproj_filename}")
1973
1973
  result = self.run_program_with_retry("dotnet", f"list {csproj_filename} package --outdated", folder, print_errors_as_information=True)
1974
- for line in result[1].replace("\r", "").split("\n"):
1974
+ for line in result[1].replace("\r", GeneralUtilities.empty_string).split("\n"):
1975
1975
  # Relevant output-lines are something like " > NJsonSchema 10.7.0 10.7.0 10.9.0"
1976
1976
  if ">" in line:
1977
- package_name = line.replace(">", "").strip().split(" ")[0]
1977
+ package_name = line.replace(">", GeneralUtilities.empty_string).strip().split(" ")[0]
1978
1978
  if not (package_name in ignored_dependencies):
1979
1979
  GeneralUtilities.write_message_to_stdout(f"Update package {package_name}...")
1980
1980
  self.run_program("dotnet", f"add {csproj_filename} package {package_name}", folder, print_errors_as_information=True)
@@ -2330,47 +2330,3 @@ TXDX
2330
2330
  @GeneralUtilities.check_arguments
2331
2331
  def install_requirementstxt_file(self, requirements_txt_file: str, folder: str, verbosity: int):
2332
2332
  self.run_program_argsasarray("pip", ["install", "-r", requirements_txt_file], folder, verbosity=verbosity)
2333
-
2334
- @GeneralUtilities.check_arguments
2335
- def update_all_services_in_docker_compose_file(self, dockercompose_file: str, version_echolon: VersionEcholon):
2336
- raise ValueError("not implemented yet")
2337
-
2338
- @GeneralUtilities.check_arguments
2339
- def update_service_in_docker_compose_file(self, dockercompose_file: str, service_name: str, version_echolon: VersionEcholon):
2340
- raise ValueError("not implemented yet")
2341
-
2342
- @GeneralUtilities.check_arguments
2343
- def get_current_version_of_service_from_docker_compose_file(self, dockercompose_file: str, service_name: str):
2344
- raise ValueError("not implemented yet")
2345
-
2346
- @GeneralUtilities.check_arguments
2347
- def get_services_from_docker_compose_file(self, dockercompose_file: str):
2348
- raise ValueError("not implemented yet")
2349
-
2350
- @GeneralUtilities.check_arguments
2351
- def get_next_version_for_nginx(self, registry_address: str, current_version: str, version_echolon: VersionEcholon):
2352
- raise ValueError("not implemented yet")
2353
-
2354
- @GeneralUtilities.check_arguments
2355
- def get_next_version_for_prometheus(self, registry_address: str, current_version: str, version_echolon: VersionEcholon):
2356
- raise ValueError("not implemented yet")
2357
-
2358
- @GeneralUtilities.check_arguments
2359
- def get_next_version_for_blackbox_exporter(self, registry_address: str, current_version: str, version_echolon: VersionEcholon):
2360
- raise ValueError("not implemented yet")
2361
-
2362
- @GeneralUtilities.check_arguments
2363
- def get_next_version_for_gitlab(self, registry_address: str, current_version: str, version_echolon: VersionEcholon):
2364
- raise ValueError("not implemented yet")
2365
-
2366
- @GeneralUtilities.check_arguments
2367
- def get_next_version_for_nextcloud(self, registry_address: str, current_version: str, version_echolon: VersionEcholon):
2368
- raise ValueError("not implemented yet")
2369
-
2370
- @GeneralUtilities.check_arguments
2371
- def get_next_version_for_genericservice(self, registry_address: str, current_version: str, version_echolon: VersionEcholon, tag_prefix: str):
2372
- raise ValueError("not implemented yet")
2373
-
2374
- @GeneralUtilities.check_arguments
2375
- def get_all_available_tags(self, registry_address: str, service: str):
2376
- raise ValueError("not implemented yet")
@@ -340,15 +340,15 @@ class TasksForCommonProjectStructure:
340
340
  filename = os.path.basename(wheel_file)
341
341
 
342
342
  if gpg_identity is None:
343
- gpg_identity_argument = ""
343
+ gpg_identity_argument = GeneralUtilities.empty_string
344
344
  else:
345
- gpg_identity_argument = "" # f" --sign --identity {gpg_identity}"
345
+ gpg_identity_argument = GeneralUtilities.empty_string # f" --sign --identity {gpg_identity}"
346
346
  # disabled due to https://blog.pypi.org/posts/2023-05-23-removing-pgp/
347
347
 
348
348
  if verbosity > 2:
349
349
  verbose_argument = " --verbose"
350
350
  else:
351
- verbose_argument = ""
351
+ verbose_argument = GeneralUtilities.empty_string
352
352
 
353
353
  twine_argument = f"upload{gpg_identity_argument} --repository {repository} --non-interactive {filename} --disable-progress-bar"
354
354
  twine_argument = f"{twine_argument} --username __token__ --password {api_key}{verbose_argument}"
@@ -649,8 +649,8 @@ class TasksForCommonProjectStructure:
649
649
  filename = os.path.basename(csproj_file)
650
650
  GeneralUtilities.write_message_to_stdout(f"Check {filename}...")
651
651
  file_content = GeneralUtilities.read_text_from_file(csproj_file)
652
- regex = regex.replace("\r", "").replace("\n", "\\n")
653
- file_content = file_content.replace("\r", "")
652
+ regex = regex.replace("\r", GeneralUtilities.empty_string).replace("\n", "\\n")
653
+ file_content = file_content.replace("\r", GeneralUtilities.empty_string)
654
654
  match = re.match(regex, file_content)
655
655
  return match is not None
656
656
 
@@ -972,13 +972,13 @@ class TasksForCommonProjectStructure:
972
972
  diff_target_file = os.path.join(diff_target_folder, "DiffReport.html")
973
973
  title = (f'Reference of codeunit {codeunitname} {codeunit_version_identifier} (contained in project <a href="{public_repository_url}">{projectname}</a> {project_version_identifier})')
974
974
  if public_repository_url is None:
975
- repo_url_html = ""
975
+ repo_url_html = GeneralUtilities.empty_string
976
976
  else:
977
977
  repo_url_html = f'<a href="{public_repository_url}/tree/{branch}/{codeunitname}">Source-code</a>'
978
978
  if codeunit_has_testcases:
979
979
  coverage_report_link = '<a href="./TestCoverageReport/index.html">Test-coverage-report</a><br>'
980
980
  else:
981
- coverage_report_link = ""
981
+ coverage_report_link = GeneralUtilities.empty_string
982
982
  index_file_for_reference = os.path.join(target_folder, "index.html")
983
983
 
984
984
  design_file = None
@@ -987,7 +987,7 @@ class TasksForCommonProjectStructure:
987
987
  design_file = GeneralUtilities.get_modest_dark_url()
988
988
  # TODO make designs from customizable sources be available by a customizable name and outsource this to a class-property because this is duplicated code.
989
989
  if design_file is None:
990
- design_html = ""
990
+ design_html = GeneralUtilities.empty_string
991
991
  else:
992
992
  design_html = f'<link type="text/css" rel="stylesheet" href="{design_file}" />'
993
993
 
@@ -1095,7 +1095,7 @@ class TasksForCommonProjectStructure:
1095
1095
  if version_identifier_of_project == "Latest":
1096
1096
  latest_version_hint = f" (v{project_version})"
1097
1097
  else:
1098
- latest_version_hint = ""
1098
+ latest_version_hint = GeneralUtilities.empty_string
1099
1099
  reference_versions_html_lines.append(f' <h2>{version_identifier_of_project}{latest_version_hint}</h2>')
1100
1100
  reference_versions_html_lines.append(" Contained codeunits:<br/>")
1101
1101
  reference_versions_html_lines.append(" <ul>")
@@ -1113,7 +1113,7 @@ class TasksForCommonProjectStructure:
1113
1113
  design_file = GeneralUtilities.get_modest_dark_url()
1114
1114
  # TODO make designs from customizable sources be available by a customizable name and outsource this to a class-property because this is duplicated code.
1115
1115
  if design_file is None:
1116
- design_html = ""
1116
+ design_html = GeneralUtilities.empty_string
1117
1117
  else:
1118
1118
  design_html = f'<link type="text/css" rel="stylesheet" href="{design_file}" />'
1119
1119
 
@@ -1423,7 +1423,7 @@ class TasksForCommonProjectStructure:
1423
1423
  self.__sc.format_xml_file(sbom_folder+f"/{codeunitname}.{codeunitversion}.sbom.xml")
1424
1424
 
1425
1425
  @GeneralUtilities.check_arguments
1426
- def push_docker_build_artifact(self, push_artifacts_file: str, registry: str, verbosity: int, push_readme: bool, commandline_arguments: list[str], repository_folder_name: str) -> None:
1426
+ def push_docker_build_artifact(self, push_artifacts_file: str, registry: str, verbosity: int, push_readme: bool, commandline_arguments: list[str], repository_folder_name: str,image_name:str=None) -> None:
1427
1427
  folder_of_this_file = os.path.dirname(push_artifacts_file)
1428
1428
  filename = os.path.basename(push_artifacts_file)
1429
1429
  codeunitname_regex: str = "([a-zA-Z0-9]+)"
@@ -1441,15 +1441,17 @@ class TasksForCommonProjectStructure:
1441
1441
  image_file = sc.find_file_by_extension(applicationimage_folder, "tar")
1442
1442
  image_filename = os.path.basename(image_file)
1443
1443
  codeunit_version = self.get_version_of_codeunit(os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml"))
1444
- image_tag_name = codeunitname.lower()
1445
- repo = f"{registry}/{image_tag_name}"
1444
+ if image_name is None:
1445
+ image_name = codeunitname
1446
+ image_name=image_name.lower()
1447
+ repo = f"{registry}/{image_name}"
1446
1448
  image_latest = f"{repo}:latest"
1447
1449
  image_version = f"{repo}:{codeunit_version}"
1448
1450
  GeneralUtilities.write_message_to_stdout("Load image...")
1449
1451
  sc.run_program("docker", f"load --input {image_filename}", applicationimage_folder, verbosity=verbosity)
1450
1452
  GeneralUtilities.write_message_to_stdout("Tag image...")
1451
- sc.run_program("docker", f"tag {image_tag_name}:{codeunit_version} {image_latest}", verbosity=verbosity)
1452
- sc.run_program("docker", f"tag {image_tag_name}:{codeunit_version} {image_version}", verbosity=verbosity)
1453
+ sc.run_program("docker", f"tag {image_name}:{codeunit_version} {image_latest}", verbosity=verbosity)
1454
+ sc.run_program("docker", f"tag {image_name}:{codeunit_version} {image_version}", verbosity=verbosity)
1453
1455
  GeneralUtilities.write_message_to_stdout("Push image...")
1454
1456
  sc.run_program("docker", f"push {image_latest}", verbosity=verbosity)
1455
1457
  sc.run_program("docker", f"push {image_version}", verbosity=verbosity)
@@ -1940,7 +1942,7 @@ class TasksForCommonProjectStructure:
1940
1942
  def set_constant(self, codeunit_folder: str, constantname: str, constant_value: str, documentationsummary: str = None, constants_valuefile: str = None) -> None:
1941
1943
  self.assert_is_codeunit_folder(codeunit_folder)
1942
1944
  if documentationsummary is None:
1943
- documentationsummary = ""
1945
+ documentationsummary = GeneralUtilities.empty_string
1944
1946
  constants_folder = os.path.join(codeunit_folder, "Other", "Resources", "Constants")
1945
1947
  GeneralUtilities.ensure_directory_exists(constants_folder)
1946
1948
  constants_metafile = os.path.join(constants_folder, f"{constantname}.constant.xml")
@@ -2259,7 +2261,7 @@ class TasksForCommonProjectStructure:
2259
2261
  codeunit_file = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
2260
2262
  root: etree._ElementTree = etree.parse(codeunit_file)
2261
2263
  ignoreddependencies = root.xpath('//cps:codeunit/cps:properties/cps:updatesettings/cps:ignoreddependencies/cps:ignoreddependency', namespaces=namespaces)
2262
- result = [x.text.replace("\\n", "").replace("\\r", "").replace("\n", "").replace("\r", "").strip() for x in ignoreddependencies]
2264
+ result = [x.text.replace("\\n", GeneralUtilities.empty_string).replace("\\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string).replace("\r", GeneralUtilities.empty_string).strip() for x in ignoreddependencies]
2263
2265
  if print_warnings_for_ignored_dependencies and len(result) > 0:
2264
2266
  GeneralUtilities.write_message_to_stderr(f"Warning: Codeunit {codeunit_name} contains the following dependencies which will are ignoed for automatic updates: "+', '.join(result))
2265
2267
  return result
@@ -2346,7 +2348,7 @@ class TasksForCommonProjectStructure:
2346
2348
  sc: ScriptCollectionCore = ScriptCollectionCore()
2347
2349
  workspace_file: str = sc.find_file_by_extension(repository_folder, "code-workspace")
2348
2350
  task_file: str = os.path.join(repository_folder, "Taskfile.yml")
2349
- lines: list[str] = ["version: '3'", "", "tasks:", ""]
2351
+ lines: list[str] = ["version: '3'", GeneralUtilities.empty_string, "tasks:", GeneralUtilities.empty_string]
2350
2352
  workspace_file_content: str = GeneralUtilities.read_text_from_file(workspace_file)
2351
2353
  jsoncontent = json.loads(workspace_file_content)
2352
2354
  tasks = jsoncontent["tasks"]["tasks"]
@@ -2389,7 +2391,7 @@ class TasksForCommonProjectStructure:
2389
2391
  aliases = task["aliases"]
2390
2392
  for alias in aliases:
2391
2393
  lines.append(f' - {alias}')
2392
- lines.append("")
2394
+ lines.append(GeneralUtilities.empty_string)
2393
2395
  GeneralUtilities.write_lines_to_file(task_file, lines)
2394
2396
 
2395
2397
  @GeneralUtilities.check_arguments
@@ -2860,21 +2862,21 @@ class TasksForCommonProjectStructure:
2860
2862
 
2861
2863
  description = self.get_codeunit_description(codeunit_file)
2862
2864
 
2863
- lines.append(f"")
2865
+ lines.append(GeneralUtilities.empty_string)
2864
2866
  lines.append(f"[{codeunitname}]")
2865
2867
  lines.append(f"note as {codeunitname}Note")
2866
2868
  lines.append(f" {description}")
2867
2869
  lines.append(f"end note")
2868
2870
  lines.append(f"{codeunitname} .. {codeunitname}Note")
2869
2871
 
2870
- lines.append(f"")
2872
+ lines.append(GeneralUtilities.empty_string)
2871
2873
  for codeunitname in codeunits:
2872
2874
  codeunit_file: str = os.path.join(repository_folder, codeunitname, f"{codeunitname}.codeunit.xml")
2873
2875
  dependent_codeunits = self.get_dependent_code_units(codeunit_file)
2874
2876
  for dependent_codeunit in dependent_codeunits:
2875
2877
  lines.append(f"{codeunitname} --> {dependent_codeunit}")
2876
2878
 
2877
- lines.append(f"")
2879
+ lines.append(GeneralUtilities.empty_string)
2878
2880
  lines.append("@enduml")
2879
2881
 
2880
2882
  GeneralUtilities.write_lines_to_file(target_file, lines)
@@ -3015,15 +3017,15 @@ class TasksForCommonProjectStructure:
3015
3017
  build_folder = os.path.join(other_folder, "Build")
3016
3018
  quality_folder = os.path.join(other_folder, "QualityCheck")
3017
3019
  reference_folder = os.path.join(other_folder, "Reference")
3018
- additional_arguments_c: str = ""
3019
- additional_arguments_b: str = ""
3020
- additional_arguments_r: str = ""
3021
- additional_arguments_l: str = ""
3022
- additional_arguments_g: str = ""
3023
- additional_arguments_f: str = ""
3020
+ additional_arguments_c: str = GeneralUtilities.empty_string
3021
+ additional_arguments_b: str = GeneralUtilities.empty_string
3022
+ additional_arguments_r: str = GeneralUtilities.empty_string
3023
+ additional_arguments_l: str = GeneralUtilities.empty_string
3024
+ additional_arguments_g: str = GeneralUtilities.empty_string
3025
+ additional_arguments_f: str = GeneralUtilities.empty_string
3024
3026
  general_argument = f' --overwrite_verbosity {str(verbosity)} --overwrite_targetenvironmenttype {target_environmenttype}'
3025
3027
 
3026
- c_additionalargumentsfile_argument = ""
3028
+ c_additionalargumentsfile_argument = GeneralUtilities.empty_string
3027
3029
 
3028
3030
  if is_pre_merge:
3029
3031
  general_argument = general_argument+" --overwrite_is_pre_merge true"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ScriptCollection
3
- Version: 3.5.119
3
+ Version: 3.5.120
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
@@ -0,0 +1,17 @@
1
+ ScriptCollection/CertificateUpdater.py,sha256=OAxrG21k_o3W3niOOGNSZzUPJlvolOWc1lRB2dMhc3g,9212
2
+ ScriptCollection/Executables.py,sha256=ht-RZFu4g34Sr09b_L0c2QpKcjLWX-wFtKSMDMZYqsw,32266
3
+ ScriptCollection/GeneralUtilities.py,sha256=hqAKRF99uu2RCSBgjX4qjYn3IlImwm6rehRvt8hbNFQ,46407
4
+ ScriptCollection/ImageUpdater.py,sha256=lel1nevTN7fgdoCDE6Xg8YY6XPsZaOQiCIyWXbmVnh0,20882
5
+ ScriptCollection/ProcessesRunner.py,sha256=3mu4ZxzZleQo0Op6o9EYTCFiJfb6kx5ov2YfZfT89mU,1395
6
+ ScriptCollection/ProgramRunnerBase.py,sha256=2kMIAqdc65UjBAddOZkzy_aFx9h5roZ5a4bQNM6RV6Y,2480
7
+ ScriptCollection/ProgramRunnerEpew.py,sha256=4pjEd0r9Fcz3TTDv0MdTSd5KkigYXcWUVI1X43regfU,6477
8
+ ScriptCollection/ProgramRunnerPopen.py,sha256=BPY7-ZMIlqT7JOKz8qlB5c0laF2Js-ijzqk09GxZC48,3821
9
+ ScriptCollection/SCLog.py,sha256=lTNw2Th5xvjf9V_pQw6DSsZPdeLAdqOelfZPW8fCC-s,3063
10
+ ScriptCollection/ScriptCollectionCore.py,sha256=9kyDdGMDZNKR7j579zjUSt3vvTLHA-vPw4_Sd2YhWmc,130059
11
+ ScriptCollection/TasksForCommonProjectStructure.py,sha256=4aEuIf89jbgKE4uMYm4DlPDe9Iu1Zujo8wPOzDld09w,234867
12
+ ScriptCollection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ scriptcollection-3.5.120.dist-info/METADATA,sha256=PbbVXi9-z9CwClIIuMdlLWkxve1wKz53TMNJYiLsdMA,7694
14
+ scriptcollection-3.5.120.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
15
+ scriptcollection-3.5.120.dist-info/entry_points.txt,sha256=3qMbfZEMhc_VTJj-bcLwB8AWcn9iXSB3l0AWpuu52Bs,3689
16
+ scriptcollection-3.5.120.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
17
+ scriptcollection-3.5.120.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,16 +0,0 @@
1
- ScriptCollection/CertificateUpdater.py,sha256=pJopWFcwaLAEVljtC4O3SVrlpIpoJNUhT1V4mgiqLvE,8970
2
- ScriptCollection/Executables.py,sha256=ht-RZFu4g34Sr09b_L0c2QpKcjLWX-wFtKSMDMZYqsw,32266
3
- ScriptCollection/GeneralUtilities.py,sha256=VO4a7xctkjN5dcBXc32gz0x9U07DEagasjvYVKqLmdM,44173
4
- ScriptCollection/ProcessesRunner.py,sha256=3mu4ZxzZleQo0Op6o9EYTCFiJfb6kx5ov2YfZfT89mU,1395
5
- ScriptCollection/ProgramRunnerBase.py,sha256=2kMIAqdc65UjBAddOZkzy_aFx9h5roZ5a4bQNM6RV6Y,2480
6
- ScriptCollection/ProgramRunnerEpew.py,sha256=4pjEd0r9Fcz3TTDv0MdTSd5KkigYXcWUVI1X43regfU,6477
7
- ScriptCollection/ProgramRunnerPopen.py,sha256=BPY7-ZMIlqT7JOKz8qlB5c0laF2Js-ijzqk09GxZC48,3821
8
- ScriptCollection/SCLog.py,sha256=GYnmS97_zQY8uzfrYQnRW9QyBTQgrKMUNfK1QHhcXC4,2384
9
- ScriptCollection/ScriptCollectionCore.py,sha256=GcAkvv1mcQYyrmhLBRlFu8bCpEbaJPm8cgQrIZXuYa4,131693
10
- ScriptCollection/TasksForCommonProjectStructure.py,sha256=OfCJsg-FLdde6Duhy4FjBcfwLr-9iLhQAimRa1ZgRyE,234045
11
- ScriptCollection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- scriptcollection-3.5.119.dist-info/METADATA,sha256=2nU78EkyhuLoi4sy4hvG9xiD4kGcswnOLWloi5NZVSg,7694
13
- scriptcollection-3.5.119.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
14
- scriptcollection-3.5.119.dist-info/entry_points.txt,sha256=3qMbfZEMhc_VTJj-bcLwB8AWcn9iXSB3l0AWpuu52Bs,3689
15
- scriptcollection-3.5.119.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
16
- scriptcollection-3.5.119.dist-info/RECORD,,