c2cciutils 1.8.0.dev63__py3-none-any.whl → 1.8.0.dev68__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.

Potentially problematic release.


This version of c2cciutils might be problematic. Click here for more details.

@@ -1,477 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """The publish script."""
4
-
5
- import argparse
6
- import os
7
- import re
8
- import subprocess # nosec
9
- import sys
10
- from re import Match
11
- from typing import Optional, cast
12
-
13
- import requests
14
- import security_md
15
- import yaml
16
-
17
- import c2cciutils
18
- import c2cciutils.configuration
19
- import c2cciutils.env
20
- import c2cciutils.lib.docker
21
- import c2cciutils.lib.oidc
22
- import c2cciutils.publish
23
- import c2cciutils.scripts.download_applications
24
- from c2cciutils.publish import GoogleCalendar
25
- from c2cciutils.scripts.trigger_image_update import dispatch
26
-
27
-
28
- def match(tpe: str, base_re: str) -> Optional[Match[str]]:
29
- """
30
- Return the match for `GITHUB_REF` basically like: `refs/<tpe>/<base_re>`.
31
-
32
- Arguments:
33
- tpe: The type of ref we want to match (heads, tag, ...)
34
- base_re: The regular expression to match the value
35
-
36
- """
37
- if base_re[0] == "^":
38
- base_re = base_re[1:]
39
- if base_re[-1] != "$":
40
- base_re += "$"
41
- return re.match(f"^refs/{tpe}/{base_re}", os.environ["GITHUB_REF"])
42
-
43
-
44
- def to_version(full_config: c2cciutils.configuration.Configuration, value: str, kind: str) -> str:
45
- """
46
- Compute publish version from branch name or tag.
47
-
48
- Arguments:
49
- full_config: The full configuration
50
- value: The value to be transformed
51
- kind: The name of the transformer in the configuration
52
-
53
- """
54
- item_re = c2cciutils.compile_re(
55
- cast(
56
- c2cciutils.configuration.VersionTransform, full_config["version"].get(kind + "_to_version_re", [])
57
- )
58
- )
59
- value_match = c2cciutils.match(value, item_re)
60
- if value_match[0] is not None:
61
- return c2cciutils.get_value(*value_match)
62
- return value
63
-
64
-
65
- def main() -> None:
66
- """Run the publish."""
67
- parser = argparse.ArgumentParser(description="Publish the project.")
68
- parser.add_argument("--group", default="default", help="The publishing group")
69
- parser.add_argument("--version", help="The version to publish to")
70
- parser.add_argument(
71
- "--docker-versions",
72
- help="The versions to publish on Docker registry, comma separated, ex: 'x,x.y,x.y.z,latest'.",
73
- )
74
- parser.add_argument("--snyk-version", help="The version to publish to Snyk")
75
- parser.add_argument("--branch", help="The branch from which to compute the version")
76
- parser.add_argument("--tag", help="The tag from which to compute the version")
77
- parser.add_argument("--dry-run", action="store_true", help="Don't do the publish")
78
- parser.add_argument(
79
- "--type",
80
- help="The type of version, if no argument provided auto-determinate, can be: "
81
- "rebuild (in case of rebuild), version_tag, version_branch, feature_branch, feature_tag "
82
- "(for pull request)",
83
- )
84
- args = parser.parse_args()
85
-
86
- config = c2cciutils.get_config()
87
- c2cciutils.env.print_environment(config)
88
-
89
- # Describe the kind of release we do: rebuild (specified with --type), version_tag, version_branch,
90
- # feature_branch, feature_tag (for pull request)
91
- version: str = ""
92
- ref = os.environ.get("GITHUB_REF", "refs/heads/fake-local")
93
- local = "GITHUB_REF" not in os.environ
94
-
95
- if len([e for e in [args.version, args.branch, args.tag] if e is not None]) > 1:
96
- print("::error::you specified more than one of the arguments --version, --branch or --tag")
97
- sys.exit(1)
98
-
99
- version_type = args.type
100
-
101
- tag_match = c2cciutils.match(
102
- ref,
103
- c2cciutils.compile_re(config["version"].get("tag_to_version_re", []), "refs/tags/"),
104
- )
105
- branch_match = c2cciutils.match(
106
- ref,
107
- c2cciutils.compile_re(config["version"].get("branch_to_version_re", []), "refs/heads/"),
108
- )
109
- ref_match = re.match(r"refs/pull/(.*)/merge", ref)
110
-
111
- if args.version is not None:
112
- version = args.version
113
- elif args.branch is not None:
114
- version = to_version(config, args.branch, "branch")
115
- elif args.tag is not None:
116
- version = to_version(config, args.tag, "tag")
117
- elif tag_match[0] is not None:
118
- if version_type is None:
119
- version_type = "version_tag"
120
- else:
121
- print("::warning::you specified the argument --type but not one of --version, --branch or --tag")
122
- version = c2cciutils.get_value(*tag_match)
123
- elif branch_match[0] is not None:
124
- if version_type is None:
125
- version_type = "version_branch"
126
- else:
127
- print("::warning::you specified the argument --type but not one of --version, --branch or --tag")
128
- version = c2cciutils.get_value(*branch_match)
129
- elif ref_match is not None:
130
- version = c2cciutils.get_value(ref_match, {}, ref)
131
- if version_type is None:
132
- version_type = "feature_branch"
133
- elif ref.startswith("refs/heads/"):
134
- if version_type is None:
135
- version_type = "feature_branch"
136
- else:
137
- print("::warning::you specified the argument --type but not one of --version, --branch or --tag")
138
- # By the way we replace '/' by '_' because it isn't supported by Docker
139
- version = "_".join(ref.split("/")[2:])
140
- elif ref.startswith("refs/tags/"):
141
- if version_type is None:
142
- version_type = "feature_tag"
143
- else:
144
- print("::warning::you specified the argument --type but not one of --version, --branch or --tag")
145
- # By the way we replace '/' by '_' because it isn't supported by Docker
146
- version = "_".join(ref.split("/")[2:])
147
- else:
148
- print(
149
- f"WARNING: {ref} is not supported, only ref starting with 'refs/heads/' or 'refs/tags/' "
150
- "are supported, ignoring"
151
- )
152
- sys.exit(0)
153
-
154
- if version_type is None:
155
- print(
156
- "::error::you specified one of the arguments --version, --branch or --tag but not the --type, GitHub ref is: {ref}"
157
- )
158
- sys.exit(1)
159
-
160
- if version_type is not None:
161
- if args.dry_run:
162
- print(f"Create release type {version_type}: {version} (dry run)")
163
- else:
164
- print(f"Create release type {version_type}: {version}")
165
-
166
- success = True
167
- pypi_config = cast(
168
- c2cciutils.configuration.PublishPypiConfig,
169
- config.get("publish", {}).get("pypi", {}) if config.get("publish", {}).get("pypi", False) else {},
170
- )
171
- if pypi_config:
172
- if pypi_config["packages"]:
173
- c2cciutils.lib.oidc.pypi_login()
174
-
175
- for package in pypi_config["packages"]:
176
- if package.get("group", c2cciutils.configuration.PUBLISH_PIP_PACKAGE_GROUP_DEFAULT) == args.group:
177
- publish = version_type in pypi_config.get("versions", [])
178
- if args.dry_run:
179
- print(
180
- f"{'Publishing' if publish else 'Checking'} "
181
- f"'{package.get('path')}' to pypi, skipping (dry run)"
182
- )
183
- else:
184
- success &= c2cciutils.publish.pip(package, version, version_type, publish)
185
-
186
- google_calendar = None
187
- google_calendar_publish = config.get("publish", {}).get("google_calendar", False) is not False
188
- google_calendar_config = cast(
189
- c2cciutils.configuration.PublishGoogleCalendarConfig,
190
- config.get("publish", {}).get("google_calendar", {}),
191
- )
192
-
193
- docker_config = cast(
194
- c2cciutils.configuration.PublishDockerConfig,
195
- config.get("publish", {}).get("docker", {}) if config.get("publish", {}).get("docker", False) else {},
196
- )
197
- if docker_config:
198
- full_repo = c2cciutils.get_repository()
199
- full_repo_split = full_repo.split("/")
200
- master_branch, _ = c2cciutils.get_master_branch(full_repo_split)
201
- security_text = ""
202
- if local:
203
- with open("SECURITY.md", encoding="utf-8") as security_file:
204
- security_text = security_file.read()
205
- else:
206
- security_response = requests.get(
207
- f"https://raw.githubusercontent.com/{full_repo}/{master_branch}/SECURITY.md",
208
- headers=c2cciutils.add_authorization_header({}),
209
- timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")),
210
- )
211
- c2cciutils.check_response(security_response, False)
212
- if security_response.ok:
213
- security_text = security_response.text
214
- elif security_response.status_code != 404:
215
- print(f"::error:: {security_response.status_code} {security_response.text}")
216
- sys.exit(1)
217
-
218
- security = security_md.Security(security_text)
219
- version_index = security.version_index
220
- alternate_tag_index = security.alternate_tag_index
221
-
222
- row_index = -1
223
- if version_index >= 0:
224
- for index, row in enumerate(security.data):
225
- if row[version_index] == version:
226
- row_index = index
227
- break
228
-
229
- alt_tags = set()
230
- if alternate_tag_index >= 0 and row_index >= 0:
231
- alt_tags = {
232
- t.strip() for t in security.data[row_index][alternate_tag_index].split(",") if t.strip()
233
- }
234
- if version_index >= 0 and security.data[-1][version_index] == version:
235
- add_latest = True
236
- for data in security.data:
237
- row_tags = {t.strip() for t in data[alternate_tag_index].split(",") if t.strip()}
238
- print(row_tags)
239
- if "latest" in row_tags:
240
- print("latest found in ", row_tags)
241
- add_latest = False
242
- break
243
- if add_latest:
244
- alt_tags.add("latest")
245
-
246
- images_src: set[str] = set()
247
- images_full: list[str] = []
248
- images_snyk: set[str] = set()
249
- versions = args.docker_versions.split(",") if args.docker_versions else [version]
250
- for image_conf in docker_config.get("images", []):
251
- if (
252
- image_conf.get("group", c2cciutils.configuration.PUBLISH_DOCKER_IMAGE_GROUP_DEFAULT)
253
- == args.group
254
- ):
255
- for tag_config in image_conf.get(
256
- "tags", c2cciutils.configuration.PUBLISH_DOCKER_IMAGE_TAGS_DEFAULT
257
- ):
258
- tag_src = tag_config.format(version="latest")
259
- image_source = f"{image_conf['name']}:{tag_src}"
260
- images_src.add(image_source)
261
- tag_snyk = tag_config.format(version=args.snyk_version or version).lower()
262
- image_snyk = f"{image_conf['name']}:{tag_snyk}"
263
-
264
- # Workaround sine we have the business plan
265
- image_snyk = f"{image_conf['name']}_{tag_snyk}"
266
-
267
- if not args.dry_run:
268
- subprocess.run(["docker", "tag", image_source, image_snyk], check=True)
269
- images_snyk.add(image_snyk)
270
- if tag_snyk != tag_src and not args.dry_run:
271
- subprocess.run(
272
- [
273
- "docker",
274
- "tag",
275
- image_source,
276
- f"{image_conf['name']}:{tag_snyk}",
277
- ],
278
- check=True,
279
- )
280
-
281
- tags_calendar = []
282
- for name, conf in {
283
- **cast(
284
- dict[str, c2cciutils.configuration.PublishDockerRepository],
285
- c2cciutils.configuration.DOCKER_REPOSITORY_DEFAULT,
286
- ),
287
- **docker_config.get("repository", {}),
288
- }.items():
289
- for docker_version in versions:
290
- tag_dst = tag_config.format(version=docker_version)
291
- if tag_dst not in tags_calendar:
292
- tags_calendar.append(tag_dst)
293
- if version_type in conf.get(
294
- "versions",
295
- c2cciutils.configuration.PUBLISH_DOCKER_REPOSITORY_VERSIONS_DEFAULT,
296
- ):
297
- tags = [
298
- tag_config.format(version=alt_tag)
299
- for alt_tag in [docker_version, *alt_tags]
300
- ]
301
-
302
- if args.dry_run:
303
- for tag in tags:
304
- print(
305
- f"Publishing {image_conf['name']}:{tag} to {name}, skipping (dry run)"
306
- )
307
- else:
308
- success &= c2cciutils.publish.docker(
309
- conf, name, image_conf, tag_src, tags, images_full
310
- )
311
-
312
- if google_calendar_publish and version_type in google_calendar_config.get(
313
- "on", c2cciutils.configuration.PUBLISH_GOOGLE_CALENDAR_ON_DEFAULT
314
- ):
315
- if not google_calendar:
316
- google_calendar = GoogleCalendar()
317
- summary = f"{image_conf['name']}:{', '.join(tags_calendar)}"
318
- description = "\n".join(
319
- [
320
- f"Published the image {image_conf['name']}",
321
- f"Published on: {', '.join(docker_config['repository'].keys())}",
322
- f"With tags: {', '.join(tags_calendar)}",
323
- f"For version type: {version_type}",
324
- ]
325
- )
326
-
327
- google_calendar.create_event(summary, description)
328
-
329
- if args.dry_run:
330
- sys.exit(0)
331
-
332
- dispatch_config = docker_config.get("dispatch", {})
333
- if dispatch_config is not False and images_full:
334
- dispatch(
335
- dispatch_config.get(
336
- "repository", c2cciutils.configuration.DOCKER_DISPATCH_REPOSITORY_DEFAULT
337
- ),
338
- dispatch_config.get(
339
- "event-type", c2cciutils.configuration.DOCKER_DISPATCH_EVENT_TYPE_DEFAULT
340
- ),
341
- images_full,
342
- )
343
-
344
- snyk_exec, env = c2cciutils.snyk_exec()
345
- for image in images_snyk:
346
- print(f"::group::Snyk check {image}")
347
- sys.stdout.flush()
348
- sys.stderr.flush()
349
- try:
350
- if version_type in ("version_branch", "version_tag"):
351
- monitor_args = docker_config.get("snyk", {}).get(
352
- "monitor_args",
353
- c2cciutils.configuration.PUBLISH_DOCKER_SNYK_MONITOR_ARGS_DEFAULT,
354
- )
355
- if monitor_args is not False:
356
- subprocess.run( # pylint: disable=subprocess-run-check
357
- [
358
- snyk_exec,
359
- "container",
360
- "monitor",
361
- *monitor_args,
362
- # Available only on the business plan
363
- # f"--project-tags=tag={image.split(':')[-1]}",
364
- image,
365
- ],
366
- env=env,
367
- )
368
- test_args = docker_config.get("snyk", {}).get(
369
- "test_args", c2cciutils.configuration.PUBLISH_DOCKER_SNYK_TEST_ARGS_DEFAULT
370
- )
371
- snyk_error = False
372
- if test_args is not False:
373
- proc = subprocess.run(
374
- [snyk_exec, "container", "test", *test_args, image],
375
- check=False,
376
- env=env,
377
- )
378
- if proc.returncode != 0:
379
- snyk_error = True
380
- print("::endgroup::")
381
- if snyk_error:
382
- print("::error::Critical vulnerability found by Snyk in the published image.")
383
- except subprocess.CalledProcessError as exception:
384
- print(f"Error: {exception}")
385
- print("::endgroup::")
386
- print("::error::With error")
387
-
388
- versions_config, dpkg_config_found = c2cciutils.lib.docker.get_versions_config()
389
- dpkg_success = True
390
- for image in images_src:
391
- dpkg_success &= c2cciutils.lib.docker.check_versions(versions_config.get(image, {}), image)
392
-
393
- if not dpkg_success:
394
- current_versions_in_images: dict[str, dict[str, str]] = {}
395
- if dpkg_config_found:
396
- with open("ci/dpkg-versions.yaml", encoding="utf-8") as dpkg_versions_file:
397
- current_versions_in_images = yaml.load(dpkg_versions_file, Loader=yaml.SafeLoader)
398
- for image in images_src:
399
- if image in current_versions_in_images:
400
- current_versions_in_images[image] = dict(current_versions_in_images[image])
401
- _, versions_image = c2cciutils.lib.docker.get_dpkg_packages_versions(image)
402
- for dpkg_package, package_version in versions_image.items():
403
- if dpkg_package not in current_versions_in_images.get(image, {}):
404
- current_versions_in_images.setdefault(image, {})[dpkg_package] = str(package_version)
405
- for dpkg_package in list(current_versions_in_images[image].keys()):
406
- if dpkg_package not in versions_image:
407
- del current_versions_in_images[image][dpkg_package]
408
- if dpkg_config_found:
409
- print(
410
- "::error::Some packages are have a greater version in the config raster then in the image."
411
- )
412
- print("Current versions of the Debian packages in Docker images:")
413
- print(yaml.dump(current_versions_in_images, Dumper=yaml.SafeDumper, default_flow_style=False))
414
- if dpkg_config_found:
415
- with open("ci/dpkg-versions.yaml", "w", encoding="utf-8") as dpkg_versions_file:
416
- yaml.dump(
417
- current_versions_in_images,
418
- dpkg_versions_file,
419
- Dumper=yaml.SafeDumper,
420
- default_flow_style=False,
421
- )
422
-
423
- if dpkg_config_found:
424
- success = False
425
-
426
- helm_config = cast(
427
- c2cciutils.configuration.PublishHelmConfig,
428
- config.get("publish", {}).get("helm", {}) if config.get("publish", {}).get("helm", False) else {},
429
- )
430
- if helm_config and helm_config["folders"] and version_type in helm_config.get("versions", []):
431
- c2cciutils.scripts.download_applications.download_c2cciutils_applications("helm/chart-releaser")
432
-
433
- owner, repo = full_repo_split
434
- commit_sha = (
435
- subprocess.run(["git", "rev-parse", "HEAD"], check=True, stdout=subprocess.PIPE)
436
- .stdout.strip()
437
- .decode()
438
- )
439
- token = (
440
- os.environ["GITHUB_TOKEN"].strip()
441
- if "GITHUB_TOKEN" in os.environ
442
- else c2cciutils.gopass("gs/ci/github/token/gopass")
443
- )
444
- assert token is not None
445
- if version_type == "version_branch":
446
- last_tag = (
447
- subprocess.run(
448
- ["git", "describe", "--abbrev=0", "--tags"], check=True, stdout=subprocess.PIPE
449
- )
450
- .stdout.strip()
451
- .decode()
452
- )
453
- expression = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$")
454
- while expression.match(last_tag) is None:
455
- last_tag = (
456
- subprocess.run(
457
- ["git", "describe", "--abbrev=0", "--tags", f"{last_tag}^"],
458
- check=True,
459
- stdout=subprocess.PIPE,
460
- )
461
- .stdout.strip()
462
- .decode()
463
- )
464
-
465
- versions = last_tag.split(".")
466
- versions[-1] = str(int(versions[-1]) + 1)
467
- version = ".".join(versions)
468
-
469
- for folder in helm_config["folders"]:
470
- success &= c2cciutils.publish.helm(folder, version, owner, repo, commit_sha, token)
471
-
472
- if not success:
473
- sys.exit(1)
474
-
475
-
476
- if __name__ == "__main__":
477
- main()
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- """Trigger an image update on the argocd repository."""
4
-
5
- import argparse
6
- import os.path
7
- import random
8
- import subprocess # nosec
9
- import sys
10
-
11
- import requests
12
- import yaml
13
-
14
-
15
- def main() -> None:
16
- """
17
- Trigger an image update on the argocd repository.
18
-
19
- Only the branch present in the HELM_RELEASE_NAMES environment variable will be considered.
20
- """
21
- parser = argparse.ArgumentParser(
22
- description="""Trigger an image update on the argocd repository.
23
-
24
- Only the branch present in the HELM_RELEASE_NAMES environment variable will be considered."""
25
- )
26
- parser.add_argument("--version", help="The version to be exported")
27
- parser.add_argument("--event-type", default="image-update", help="The event name to be triggered")
28
- parser.add_argument(
29
- "--repository",
30
- default="camptocamp/argocd-gs-platform-ch-development-apps",
31
- help="The repository name to be triggered",
32
- )
33
-
34
- args = parser.parse_args()
35
-
36
- if args.version:
37
- version = args.version
38
- else:
39
- ref = os.environ["GITHUB_REF"].split("/")
40
-
41
- if ref[1] != "heads":
42
- print("::error::Not a branch")
43
- sys.exit(0)
44
-
45
- version = "/".join(ref[2:])
46
-
47
- if version not in os.environ.get("HELM_RELEASE_NAMES", "").split(","):
48
- print("::error::Not a release branch")
49
- sys.exit(0)
50
-
51
- images_full = []
52
- with open("ci/config.yaml", encoding="utf-8") as config_file:
53
- ci_config = yaml.load(config_file, Loader=yaml.SafeLoader)
54
- for image_config in ci_config.get("publish", {}).get("docker", {}).get("images", []):
55
- images_full.append(image_config["name"])
56
-
57
- dispatch(args.repository, args.event_type, [f"{image}:{version}" for image in images_full])
58
-
59
-
60
- def dispatch(repository: str, event_type: str, images_full: list[str]) -> None:
61
- """Trigger an image update on the argocd repository."""
62
- id_ = random.randint(1, 100000) # noqa: S311 # nosec
63
- print(f"Triggering {event_type}:{id_} on {repository} with {','.join(images_full)}")
64
-
65
- response = requests.post(
66
- f"https://api.github.com/repos/{repository}/dispatches",
67
- headers={
68
- "Content-Type": "application/json2",
69
- "Accept": "application/vnd.github.v3+json",
70
- "Authorization": "token "
71
- + subprocess.run(
72
- ["gopass", "show", "gs/ci/github/token/gopass"], check=True, stdout=subprocess.PIPE
73
- )
74
- .stdout.decode()
75
- .strip(),
76
- },
77
- json={"event_type": event_type, "client_payload": {"name": " ".join(images_full), "id": id_}},
78
- timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")),
79
- )
80
- response.raise_for_status()
81
-
82
-
83
- if __name__ == "__main__":
84
- main()