c2cciutils 1.8.0.dev68__tar.gz → 1.9.0.dev29__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/LICENSE +1 -1
  2. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/PKG-INFO +9 -10
  3. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/__init__.py +28 -19
  4. c2cciutils-1.9.0.dev29/c2cciutils/applications-versions.yaml +2 -0
  5. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/configuration.py +22 -18
  6. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/env.py +9 -6
  7. c2cciutils-1.9.0.dev29/c2cciutils/scripts/__init__.py +3 -0
  8. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/docker_logs.py +17 -13
  9. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/env.py +1 -0
  10. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/__init__.py +2 -0
  11. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/db.py +16 -15
  12. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/install.py +6 -3
  13. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/logs.py +13 -12
  14. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/wait.py +16 -12
  15. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/main.py +3 -3
  16. c2cciutils-1.9.0.dev29/pyproject.toml +111 -0
  17. c2cciutils-1.8.0.dev68/c2cciutils/applications-versions.yaml +0 -3
  18. c2cciutils-1.8.0.dev68/c2cciutils/applications.yaml +0 -22
  19. c2cciutils-1.8.0.dev68/c2cciutils/applications_definition.py +0 -51
  20. c2cciutils-1.8.0.dev68/c2cciutils/scripts/__init__.py +0 -1
  21. c2cciutils-1.8.0.dev68/c2cciutils/scripts/download_applications.py +0 -108
  22. c2cciutils-1.8.0.dev68/pyproject.toml +0 -97
  23. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/README.md +0 -0
  24. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/branches.graphql +0 -0
  25. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/commits.graphql +0 -0
  26. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/default_branch.graphql +0 -0
  27. {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/schema.json +0 -0
@@ -1,4 +1,4 @@
1
- Copyright (c) 2020-2025, Camptocamp SA
1
+ Copyright (c) 2020-2026, Camptocamp SA
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -1,29 +1,28 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: c2cciutils
3
- Version: 1.8.0.dev68
3
+ Version: 1.9.0.dev29
4
4
  Summary: Common utilities for Camptocamp CI
5
5
  License: FreeBSD
6
+ License-File: LICENSE
6
7
  Keywords: ci
7
8
  Author: Camptocamp
8
9
  Author-email: info@camptocamp.com
9
- Requires-Python: >=3.9,<4.0
10
+ Requires-Python: >=3.11
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Environment :: Console
12
13
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: License :: Other/Proprietary License
15
14
  Classifier: Operating System :: OS Independent
16
15
  Classifier: Programming Language :: Python
17
16
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
- Classifier: Programming Language :: Python :: 3.10
20
17
  Classifier: Programming Language :: Python :: 3.11
21
18
  Classifier: Programming Language :: Python :: 3.12
22
19
  Classifier: Programming Language :: Python :: 3.13
23
20
  Classifier: Typing :: Typed
24
- Requires-Dist: PyYAML (==6.0.2)
25
- Requires-Dist: requests (==2.32.3)
26
- Requires-Dist: ruamel.yaml (==0.18.10)
21
+ Requires-Dist: PyYAML (>=6,<7)
22
+ Requires-Dist: applications-download (>=1,<2)
23
+ Requires-Dist: requests (>=2,<3)
24
+ Requires-Dist: ruamel.yaml (>=0,<1)
25
+ Project-URL: Bug Tracker, https://github.com/camptocamp/c2cciutils/issues
27
26
  Project-URL: Repository, https://github.com/camptocamp/c2cciutils
28
27
  Description-Content-Type: text/markdown
29
28
 
@@ -1,3 +1,5 @@
1
+ # Copyright (c) 2020-2026, Camptocamp SA
2
+
1
3
  """c2cciutils shared utils function."""
2
4
 
3
5
  import json
@@ -5,7 +7,8 @@ import os.path
5
7
  import re
6
8
  import subprocess # nosec
7
9
  import sys
8
- from typing import Any, Optional, cast
10
+ from pathlib import Path
11
+ from typing import Any, cast
9
12
 
10
13
  import requests
11
14
  import ruamel.yaml
@@ -18,7 +21,7 @@ def get_repository() -> str:
18
21
  if "GITHUB_REPOSITORY" in os.environ:
19
22
  return os.environ["GITHUB_REPOSITORY"]
20
23
 
21
- remote_lines = subprocess.check_output(["git", "remote", "--verbose"]).decode().split("\n")
24
+ remote_lines = subprocess.check_output(["git", "remote", "--verbose"]).decode().split("\n") # noqa: S607
22
25
  remote_match = (
23
26
  re.match(r".*git@github.com:(.*).git .*", remote_lines[0]) if len(remote_lines) >= 1 else None
24
27
  )
@@ -37,7 +40,9 @@ def get_master_branch(repo: list[str]) -> tuple[str, bool]:
37
40
  success = False
38
41
  try:
39
42
  default_branch_json = graphql(
40
- "default_branch.graphql", {"name": repo[1], "owner": repo[0]}, default=False
43
+ "default_branch.graphql",
44
+ {"name": repo[1], "owner": repo[0]},
45
+ default=False,
41
46
  )
42
47
  success = default_branch_json is not False
43
48
  master_branch = default_branch_json["repository"]["defaultBranchRef"]["name"] if success else "master"
@@ -50,8 +55,9 @@ def get_master_branch(repo: list[str]) -> tuple[str, bool]:
50
55
  def get_config() -> c2cciutils.configuration.Configuration:
51
56
  """Get the configuration, with project and auto detections."""
52
57
  config: c2cciutils.configuration.Configuration = {}
53
- if os.path.exists("ci/config.yaml"):
54
- with open("ci/config.yaml", encoding="utf-8") as open_file:
58
+ config_path = Path("ci/config.yaml")
59
+ if config_path.exists():
60
+ with config_path.open(encoding="utf-8") as open_file:
55
61
  yaml_ = ruamel.yaml.YAML()
56
62
  config = yaml_.load(open_file)
57
63
 
@@ -61,9 +67,9 @@ def get_config() -> c2cciutils.configuration.Configuration:
61
67
  def error(
62
68
  checker: str,
63
69
  message: str,
64
- file: Optional[str] = None,
65
- line: Optional[int] = None,
66
- col: Optional[int] = None,
70
+ file: str | None = None,
71
+ line: int | None = None,
72
+ col: int | None = None,
67
73
  error_type: str = "error",
68
74
  ) -> None:
69
75
  """
@@ -114,7 +120,7 @@ def print_versions(config: c2cciutils.configuration.PrintVersions) -> bool:
114
120
  try:
115
121
  sys.stdout.flush()
116
122
  sys.stderr.flush()
117
- current_version = subprocess.check_output(version.get("cmd", [])).decode()
123
+ current_version = subprocess.check_output(version.get("cmd", [])).decode() # noqa: S603
118
124
  print(f"{version.get('prefix', '')}{current_version}")
119
125
  except PermissionError as exception:
120
126
  error(
@@ -138,7 +144,7 @@ def print_versions(config: c2cciutils.configuration.PrintVersions) -> bool:
138
144
  return True
139
145
 
140
146
 
141
- def gopass(key: str, default: Optional[str] = None) -> Optional[str]:
147
+ def gopass(key: str, default: str | None = None) -> str | None:
142
148
  """
143
149
  Get a value from gopass.
144
150
 
@@ -150,7 +156,7 @@ def gopass(key: str, default: Optional[str] = None) -> Optional[str]:
150
156
 
151
157
  """
152
158
  try:
153
- return subprocess.check_output(["gopass", "show", key]).strip().decode()
159
+ return subprocess.check_output(["gopass", "show", key]).strip().decode() # noqa: S603,S607
154
160
  except FileNotFoundError:
155
161
  if default is not None:
156
162
  return default
@@ -174,9 +180,10 @@ def add_authorization_header(headers: dict[str, str]) -> dict[str, str]:
174
180
  else gopass("gs/ci/github/token/gopass")
175
181
  )
176
182
  headers["Authorization"] = f"Bearer {token}"
177
- return headers
178
183
  except FileNotFoundError:
179
184
  return headers
185
+ else:
186
+ return headers
180
187
 
181
188
 
182
189
  def check_response(response: requests.Response, raise_for_status: bool = True) -> Any:
@@ -205,7 +212,7 @@ def graphql(query_file: str, variables: dict[str, Any], default: Any = None) ->
205
212
  In case of error it throw an exception
206
213
 
207
214
  """
208
- with open(os.path.join(os.path.dirname(__file__), query_file), encoding="utf-8") as query_open:
215
+ with (Path(__file__).parent / query_file).open(encoding="utf-8") as query_open:
209
216
  query = query_open.read()
210
217
 
211
218
  http_response = requests.post(
@@ -214,24 +221,26 @@ def graphql(query_file: str, variables: dict[str, Any], default: Any = None) ->
214
221
  {
215
222
  "query": query,
216
223
  "variables": variables,
217
- }
224
+ },
218
225
  ),
219
226
  headers=add_authorization_header(
220
227
  {
221
228
  "Content-Type": "application/json",
222
- }
229
+ },
223
230
  ),
224
231
  timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")),
225
232
  )
226
233
  if http_response.status_code in (401, 403) and default is not None:
227
234
  print(f"::warning::GraphQL error: {http_response.status_code}, use default value")
228
- check_response(http_response, False)
235
+ check_response(http_response, raise_for_status=False)
229
236
  return default
230
237
  check_response(http_response)
231
238
  json_response = http_response.json()
232
239
 
233
240
  if "errors" in json_response:
234
- raise RuntimeError(f"GraphQL error: {json.dumps(json_response['errors'], indent=2)}")
241
+ message = f"GraphQL error: {json.dumps(json_response['errors'], indent=2)}"
242
+ raise RuntimeError(message)
235
243
  if "data" not in json_response:
236
- raise RuntimeError(f"GraphQL no data: {json.dumps(json_response, indent=2)}")
237
- return cast(dict[str, Any], json_response["data"])
244
+ message = f"GraphQL no data: {json.dumps(json_response, indent=2)}"
245
+ raise RuntimeError(message)
246
+ return cast("dict[str, Any]", json_response["data"])
@@ -0,0 +1,2 @@
1
+ # https://docs.renovatebot.com/modules/datasource/#github-releases-datasource
2
+ postgresql: 18.12.1 # helm - https://charts.bitnami.com/bitnami
@@ -1,24 +1,28 @@
1
- """Automatically generated file from a JSON schema."""
1
+ # Copyright (c) 2020-2026, Camptocamp SA
2
+
3
+ """
4
+ Automatically generated file from a JSON schema.
5
+ """
2
6
 
3
7
  from typing import Any, TypedDict
4
8
 
5
9
 
6
10
  class Configuration(TypedDict, total=False):
7
- """
11
+ r"""
8
12
  configuration.
9
13
 
10
14
  C2C CI utils configuration file
11
15
  """
12
16
 
13
17
  print_versions: "PrintVersions"
14
- """
18
+ r"""
15
19
  Print versions.
16
20
 
17
21
  The print versions configuration
18
22
  """
19
23
 
20
24
  k8s: "K8SConfiguration"
21
- """
25
+ r"""
22
26
  K8s configuration.
23
27
 
24
28
  default:
@@ -27,7 +31,7 @@ class Configuration(TypedDict, total=False):
27
31
 
28
32
 
29
33
  DB_CONFIGURATION_DEFAULT: dict[str, Any] = {}
30
- """ Default value of the field path 'K8s configuration db' """
34
+ r""" Default value of the field path 'K8s configuration db' """
31
35
 
32
36
 
33
37
  # | DB configuration.
@@ -76,15 +80,15 @@ K3DConfiguration = TypedDict(
76
80
 
77
81
 
78
82
  K3D_CONFIGURATION_DEFAULT: dict[str, Any] = {}
79
- """ Default value of the field path 'K8s configuration k3d' """
83
+ r""" Default value of the field path 'K8s configuration k3d' """
80
84
 
81
85
 
82
86
  K3D_INSTALL_COMMANDS_DEFAULT = [["k3d", "cluster", "create", "test-cluster", "--no-lb", "--no-rollback"]]
83
- """ Default value of the field path 'K3d configuration install-commands' """
87
+ r""" Default value of the field path 'K3d configuration install-commands' """
84
88
 
85
89
 
86
90
  class K8SConfiguration(TypedDict, total=False):
87
- """
91
+ r"""
88
92
  K8s configuration.
89
93
 
90
94
  default:
@@ -92,7 +96,7 @@ class K8SConfiguration(TypedDict, total=False):
92
96
  """
93
97
 
94
98
  k3d: "K3DConfiguration"
95
- """
99
+ r"""
96
100
  K3d configuration.
97
101
 
98
102
  default:
@@ -100,7 +104,7 @@ class K8SConfiguration(TypedDict, total=False):
100
104
  """
101
105
 
102
106
  db: "DbConfiguration"
103
- """
107
+ r"""
104
108
  DB configuration.
105
109
 
106
110
  Database configuration
@@ -111,7 +115,7 @@ class K8SConfiguration(TypedDict, total=False):
111
115
 
112
116
 
113
117
  K8S_CONFIGURATION_DEFAULT: dict[str, Any] = {}
114
- """ Default value of the field path 'configuration k8s' """
118
+ r""" Default value of the field path 'configuration k8s' """
115
119
 
116
120
 
117
121
  K8S_DB_CHART_OPTIONS_DEFAULT = {
@@ -121,7 +125,7 @@ K8S_DB_CHART_OPTIONS_DEFAULT = {
121
125
  "auth.postgresPassword": "mySuperTestingPassword",
122
126
  "volumePermissions.enabled": "true",
123
127
  }
124
- """ Default value of the field path 'DB configuration chart-options' """
128
+ r""" Default value of the field path 'DB configuration chart-options' """
125
129
 
126
130
 
127
131
  PRINT_VERSIONS_VERSIONS_DEFAULT = [
@@ -135,18 +139,18 @@ PRINT_VERSIONS_VERSIONS_DEFAULT = [
135
139
  {"name": "java", "cmd": ["java", "-version"]},
136
140
  {"name": "helm", "cmd": ["helm", "version"], "prefix": "HELM: "},
137
141
  ]
138
- """ Default value of the field path 'Print versions versions' """
142
+ r""" Default value of the field path 'Print versions versions' """
139
143
 
140
144
 
141
145
  class PrintVersions(TypedDict, total=False):
142
- """
146
+ r"""
143
147
  Print versions.
144
148
 
145
149
  The print versions configuration
146
150
  """
147
151
 
148
152
  versions: list["_PrintVersionsVersionsItem"]
149
- """
153
+ r"""
150
154
  Print versions versions.
151
155
 
152
156
  default:
@@ -197,10 +201,10 @@ class PrintVersions(TypedDict, total=False):
197
201
 
198
202
  class _PrintVersionsVersionsItem(TypedDict, total=False):
199
203
  cmd: list[str]
200
- """ The command that should be used """
204
+ r""" The command that should be used """
201
205
 
202
206
  name: str
203
- """ The name """
207
+ r""" The name """
204
208
 
205
209
  prefix: str
206
- """ Prefix added when we print the version """
210
+ r""" Prefix added when we print the version """
@@ -1,7 +1,10 @@
1
+ # Copyright (c) 2020-2026, Camptocamp SA
2
+
1
3
  import json
2
4
  import os
3
5
  import subprocess # nosec
4
6
  import sys
7
+ from pathlib import Path
5
8
 
6
9
  import ruamel.yaml
7
10
  import yaml
@@ -45,7 +48,7 @@ def print_environment_variables() -> None:
45
48
  def print_github_event_file() -> None:
46
49
  """Print the GitHub event file."""
47
50
  if "GITHUB_EVENT_PATH" in os.environ:
48
- with open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8") as event:
51
+ with Path(os.environ["GITHUB_EVENT_PATH"]).open(encoding="utf-8") as event:
49
52
  print(event.read())
50
53
 
51
54
 
@@ -57,17 +60,17 @@ def print_github_event_object() -> None:
57
60
 
58
61
  def print_python_package_version() -> None:
59
62
  """Print the version of the Python packages."""
60
- subprocess.run(["python3", "-m", "pip", "freeze", "--all"]) # pylint: disable=subprocess-run-check
63
+ subprocess.run(["python3", "-m", "pip", "freeze", "--all"], check=False) # pylint: disable=subprocess-run-check # noqa: S607
61
64
 
62
65
 
63
66
  def print_node_package_version() -> None:
64
67
  """Print the version of the Python packages."""
65
- subprocess.run(["npm", "list", "--global"]) # pylint: disable=subprocess-run-check
68
+ subprocess.run(["npm", "list", "--global"], check=False) # pylint: disable=subprocess-run-check # noqa: S607
66
69
 
67
70
 
68
71
  def print_debian_package_version() -> None:
69
72
  """Print the version of the Python packages."""
70
- subprocess.run(["dpkg", "--list"]) # pylint: disable=subprocess-run-check
73
+ subprocess.run(["dpkg", "--list"], check=False) # pylint: disable=subprocess-run-check # noqa: S607
71
74
 
72
75
 
73
76
  def print_environment(config: c2cciutils.configuration.Configuration, prefix: str = "Print ") -> None:
@@ -90,7 +93,7 @@ def print_environment(config: c2cciutils.configuration.Configuration, prefix: st
90
93
  ("Python package versions", print_python_package_version),
91
94
  ("Node package versions", print_node_package_version),
92
95
  ("Debian package versions", print_debian_package_version),
93
- ]
96
+ ],
94
97
  )
95
98
 
96
99
  for name, function in functions:
@@ -101,7 +104,7 @@ def print_environment(config: c2cciutils.configuration.Configuration, prefix: st
101
104
  try:
102
105
  sys.stdout.flush()
103
106
  sys.stderr.flush()
104
- function() # type: ignore
107
+ function() # type: ignore[operator]
105
108
  except subprocess.CalledProcessError as error:
106
109
  print(f"::error::Error: {error}")
107
110
  print("::endgroup::")
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2020-2026, Camptocamp SA
2
+
3
+ """The scripts main functions."""
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  import argparse
4
- import os
5
+ import datetime
5
6
  import subprocess # nosec
6
7
  import sys
7
- from datetime import datetime
8
+ from pathlib import Path
8
9
 
9
10
 
10
11
  def _print(message: str) -> None:
@@ -15,38 +16,41 @@ def _print(message: str) -> None:
15
16
  def main() -> None:
16
17
  """Print the list of running docker containers and their logs formatted for GitHub CI."""
17
18
  parser = argparse.ArgumentParser(
18
- description=("Print the list of running docker containers and their logs formatted for GitHub CI.")
19
+ description=("Print the list of running docker containers and their logs formatted for GitHub CI."),
19
20
  )
20
21
  parser.parse_args()
21
22
 
22
- if os.path.exists("docker-compose.yaml"):
23
+ if Path("docker-compose.yaml").exists():
23
24
  _print("::group::Docker Compose ps")
24
- subprocess.run(["docker", "compose", "ps", "--all"], check=False)
25
+ subprocess.run(["docker", "compose", "ps", "--all"], check=False) # noqa: S607
25
26
  _print("::endgroup::")
26
27
 
27
28
  _print("::group::Docker ps")
28
- subprocess.run(["docker", "ps"], check=False)
29
+ subprocess.run(["docker", "ps", "--all"], check=False) # noqa: S607
29
30
  _print("::endgroup::")
30
31
 
31
32
  # Store in /tmp/docker-logs-timestamp the current timestamp to avoid printing same logs multiple times.
32
33
  timestamp_args = []
33
- if os.path.exists("/tmp/docker-logs-timestamp"): # noqa: S108 # nosec
34
- with open("/tmp/docker-logs-timestamp", encoding="utf-8") as timestamp_file: # noqa: S108 # nosec
34
+ timestamp_file_path = Path("/tmp/docker-logs-timestamp") # noqa: S108 # nosec
35
+ if timestamp_file_path.exists(): # nosec
36
+ with timestamp_file_path.open(encoding="utf-8") as timestamp_file: # nosec
35
37
  timestamp_args = [f"--since={timestamp_file.read().strip()}Z"]
36
38
 
37
- with open("/tmp/docker-logs-timestamp", "w", encoding="utf-8") as timestamp_file: # noqa: S108 # nosec
38
- timestamp_file.write(datetime.utcnow().isoformat())
39
+ with timestamp_file_path.open("w", encoding="utf-8") as timestamp_file: # nosec
40
+ timestamp_file.write(datetime.datetime.now(tz=datetime.UTC).isoformat())
39
41
 
40
42
  for name in (
41
- subprocess.run(
42
- ["docker", "ps", "--all", "--format", "{{ .Names }}"], check=True, stdout=subprocess.PIPE
43
+ subprocess.run( # noqa: S603,S607,RUF100
44
+ ["docker", "ps", "--all", "--format", "{{ .Names }}"], # noqa: S607
45
+ check=True,
46
+ stdout=subprocess.PIPE,
43
47
  )
44
48
  .stdout.decode()
45
49
  .split("\n")
46
50
  ):
47
51
  if name:
48
52
  _print(f"::group::{name}: New logs")
49
- subprocess.run(["docker", "logs"] + timestamp_args + [name], check=False)
53
+ subprocess.run(["docker", "logs", *timestamp_args, name], check=False) # noqa: S603,S607
50
54
  _print("::endgroup::")
51
55
 
52
56
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  """The checker main function."""
4
5
 
@@ -1 +1,3 @@
1
+ # Copyright (c) 2020-2026, Camptocamp SA
2
+
1
3
  """The commands that can be used to test a chart on Kubernetes."""
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  import argparse
4
5
  import os
5
6
  import subprocess # nosec
6
7
  import sys
8
+ from pathlib import Path
7
9
  from typing import cast
8
10
 
9
11
  import yaml
@@ -33,36 +35,34 @@ def main() -> None:
33
35
  parser.add_argument("--cleanup", action="store_true", help="Drop the database")
34
36
 
35
37
  config = c2cciutils.get_config()
36
- with open(
37
- os.path.join(
38
- os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "applications-versions.yaml"
39
- ),
40
- encoding="utf-8",
41
- ) as config_file:
42
- versions = cast(dict[str, str], yaml.load(config_file, Loader=yaml.SafeLoader))
38
+ config_path = Path(__file__).resolve().parents[2] / "applications-versions.yaml"
39
+ with config_path.open(encoding="utf-8") as config_file:
40
+ versions = cast("dict[str, str]", yaml.load(config_file, Loader=yaml.SafeLoader))
43
41
 
44
42
  args = parser.parse_args()
45
43
 
46
44
  if args.cleanup:
47
45
  _print("::group::Cleanup the database")
48
- subprocess.run([os.environ.get("HELM", "helm"), "uninstall", "test-pg"], check=False)
46
+ subprocess.run([os.environ.get("HELM", "helm"), "uninstall", "test-pg"], check=False) # noqa: S603
49
47
  _print("::endgroup::")
50
48
  sys.exit(0)
51
49
 
52
50
  _print("::group::Add repo")
53
- subprocess.run(
51
+ subprocess.run( # noqa: S603
54
52
  [os.environ.get("HELM", "helm"), "repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
55
53
  check=True,
56
54
  )
57
55
  _print("::endgroup::")
58
56
 
59
57
  _print("::group::Install chart")
60
- subprocess.run(
58
+ subprocess.run( # noqa: S603
61
59
  [
62
60
  os.environ.get("HELM", "helm"),
63
61
  "install",
64
62
  "test-pg",
65
63
  f"--version={versions['postgresql']}",
64
+ "--set=image.repository=bitnamilegacy/postgresql",
65
+ "--set=volumePermissions.image.repository=bitnamilegacy/os-shell",
66
66
  ]
67
67
  + [
68
68
  f"--set={k}={v}"
@@ -77,21 +77,22 @@ def main() -> None:
77
77
  _print("::endgroup::")
78
78
 
79
79
  _print("::group::Wait ready")
80
- subprocess.run(["c2cciutils-k8s-wait", "--selector=app.kubernetes.io/name=postgresql"], check=True)
80
+ subprocess.run(["c2cciutils-k8s-wait", "--selector=app.kubernetes.io/name=postgresql"], check=True) # noqa: S607
81
81
  _print("::endgroup::")
82
82
 
83
83
  if args.script:
84
84
  _print("::group::Add data")
85
- with open(args.script, encoding="utf-8") as script:
86
- subprocess.run(
87
- [
85
+ script_path = Path(args.script)
86
+ with script_path.open(encoding="utf-8") as script:
87
+ subprocess.run( # noqa: S603,S607,RUF100
88
+ [ # noqa: S607
88
89
  "kubectl",
89
90
  "run",
90
91
  "test-pg-postgresql-client",
91
92
  "--rm",
92
93
  "--restart=Never",
93
94
  "--namespace=default",
94
- "--image=docker.io/bitnami/postgresql",
95
+ "--image=docker.io/bitnamilegacy/postgresql",
95
96
  "--env=PGPASSWORD=mySuperTestingPassword",
96
97
  "--stdin=true",
97
98
  "--command",
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  import argparse
4
5
  import subprocess # nosec
5
6
  import sys
6
7
 
8
+ import applications_download
9
+
7
10
  import c2cciutils
8
11
  import c2cciutils.configuration
9
- import c2cciutils.scripts.download_applications
10
12
 
11
13
 
12
14
  def _print(message: str) -> None:
@@ -22,7 +24,8 @@ def main() -> None:
22
24
  config = c2cciutils.get_config()
23
25
 
24
26
  _print("::group::Install")
25
- c2cciutils.scripts.download_applications.download_c2cciutils_applications("k3d-io/k3d")
27
+ apps = applications_download.Applications()
28
+ apps.install("k3d-io/k3d")
26
29
  _print("::endgroup::")
27
30
 
28
31
  _print("::group::Create cluster")
@@ -31,7 +34,7 @@ def main() -> None:
31
34
  .get("k3d", {})
32
35
  .get("install-commands", c2cciutils.configuration.K3D_INSTALL_COMMANDS_DEFAULT)
33
36
  ):
34
- subprocess.run(cmd, check=True)
37
+ subprocess.run(cmd, check=True) # noqa: S603
35
38
  _print("::endgroup::")
36
39
 
37
40
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  import argparse
4
5
  import subprocess # nosec
@@ -18,34 +19,34 @@ def main() -> None:
18
19
  args = parser.parse_args()
19
20
 
20
21
  if args.namespace:
21
- subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=default"], check=True)
22
+ subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=default"], check=True) # noqa: S607
22
23
 
23
24
  try:
24
25
  _print("::group::Events")
25
- subprocess.run(["kubectl", "get", "events"], check=False)
26
+ subprocess.run(["kubectl", "get", "events"], check=False) # noqa: S607
26
27
  _print("::endgroup::")
27
28
 
28
29
  _print("::group::Deployments")
29
- subprocess.run(["kubectl", "get", "deployments", "--output=wide"], check=False)
30
+ subprocess.run(["kubectl", "get", "deployments", "--output=wide"], check=False) # noqa: S607
30
31
  _print("::endgroup::")
31
32
 
32
33
  _print("::group::Pods")
33
- subprocess.run(["kubectl", "get", "pods", "--output=wide"], check=False)
34
+ subprocess.run(["kubectl", "get", "pods", "--output=wide"], check=False) # noqa: S607
34
35
  _print("::endgroup::")
35
36
 
36
37
  for name in (
37
- subprocess.run(["kubectl", "get", "pods", "--output=name"], check=True, stdout=subprocess.PIPE)
38
+ subprocess.run(["kubectl", "get", "pods", "--output=name"], check=True, stdout=subprocess.PIPE) # noqa: S607
38
39
  .stdout.decode()
39
40
  .split("\n")
40
41
  ):
41
42
  if name:
42
43
  _print(f"::group::{name}: Describe")
43
- subprocess.run(["kubectl", "describe", name], check=False)
44
+ subprocess.run(["kubectl", "describe", name], check=False) # noqa: S603,S607
44
45
  _print("::endgroup::")
45
46
 
46
47
  for container in (
47
- subprocess.run(
48
- ["kubectl", "get", name, "--output=jsonpath={.spec.initContainers[*].name}"],
48
+ subprocess.run( # noqa: S603,S607,RUF100
49
+ ["kubectl", "get", name, "--output=jsonpath={.spec.initContainers[*].name}"], # noqa: S607
49
50
  check=True,
50
51
  stdout=subprocess.PIPE,
51
52
  )
@@ -54,12 +55,12 @@ def main() -> None:
54
55
  ):
55
56
  if name:
56
57
  _print(f"::group::{name} {container}: Logs")
57
- subprocess.run(["kubectl", "logs", name, container], check=False)
58
+ subprocess.run(["kubectl", "logs", name, container], check=False) # noqa: S603,S607
58
59
  _print("::endgroup::")
59
60
 
60
61
  for container in (
61
- subprocess.run(
62
- ["kubectl", "get", name, "--output=jsonpath={.spec.containers[*].name}"],
62
+ subprocess.run( # noqa: S603,S607,RUF100
63
+ ["kubectl", "get", name, "--output=jsonpath={.spec.containers[*].name}"], # noqa: S607
63
64
  check=True,
64
65
  stdout=subprocess.PIPE,
65
66
  )
@@ -67,7 +68,7 @@ def main() -> None:
67
68
  .split()
68
69
  ):
69
70
  _print(f"::group::{name} {container}: Logs")
70
- subprocess.run(["kubectl", "logs", name, container], check=False)
71
+ subprocess.run(["kubectl", "logs", name, container], check=False) # noqa: S603,S607
71
72
  _print("::endgroup::")
72
73
  except subprocess.CalledProcessError as exception:
73
74
  # No exit error
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  import argparse
4
5
  import json
@@ -17,7 +18,7 @@ def _check_deployment_status(deployments: Any) -> bool:
17
18
  for condition in deployment["status"].get("conditions", []):
18
19
  if not condition["status"]:
19
20
  print(
20
- f"::group::Deployment {deployment['metadata']['name']} not ready: {condition['message']}"
21
+ f"::group::Deployment {deployment['metadata']['name']} not ready: {condition['message']}",
21
22
  )
22
23
  print(json.dumps(condition, indent=4))
23
24
  print("::endgroup::")
@@ -26,7 +27,7 @@ def _check_deployment_status(deployments: Any) -> bool:
26
27
  if deployment["status"].get("unavailableReplicas", 0) != 0:
27
28
  print(
28
29
  f"::group::Deployment {deployment['metadata']['name']} not ready there is {deployment['status'].get('unavailableReplicas', 0)} "
29
- "unavailable replicas"
30
+ "unavailable replicas",
30
31
  )
31
32
  print(json.dumps(deployment["status"], indent=4))
32
33
  print("::endgroup::")
@@ -42,7 +43,7 @@ def _check_container_status(pod: Any, status: Any, is_init: bool = False) -> boo
42
43
  if not good:
43
44
  waiting = status["state"].get("waiting")
44
45
  terminated = status["state"].get("terminated")
45
- state = waiting if waiting else terminated
46
+ state = waiting or terminated
46
47
  status_message = state.get("message", state.get("reason", "")) if state else ""
47
48
  if not status_message:
48
49
  state = status.get("lastState", {}).get("terminated", {})
@@ -54,7 +55,7 @@ def _check_container_status(pod: Any, status: Any, is_init: bool = False) -> boo
54
55
  status_message = status_message.strip()
55
56
  if status_message == "Completed":
56
57
  return True
57
- print(f"::group::Container not ready in {pod['metadata']['name']}: {status_message}") # noqa: E713
58
+ print(f"::group::Container not ready in {pod['metadata']['name']}: {status_message}")
58
59
  if status_message_long != status_message:
59
60
  print(status_message_long)
60
61
  print(json.dumps(status, indent=4))
@@ -68,14 +69,14 @@ def _check_pod_status(pods: Any) -> bool:
68
69
  for condition in pod["status"].get("conditions", []):
69
70
  if not condition["status"]:
70
71
  print(
71
- f"::group::Pod not ready in {pod['metadata']['name']}: {condition.get('message', condition['type'])}" # noqa: E713
72
+ f"::group::Pod not ready in {pod['metadata']['name']}: {condition.get('message', condition['type'])}",
72
73
  )
73
74
  print(json.dumps(condition, indent=4))
74
75
  print("::endgroup::")
75
76
  return False
76
77
 
77
78
  for status in pod["status"].get("initContainerStatuses", []):
78
- if not _check_container_status(pod, status, True):
79
+ if not _check_container_status(pod, status, is_init=True):
79
80
  return False
80
81
  for status in pod["status"].get("containerStatuses", []):
81
82
  if not _check_container_status(pod, status):
@@ -100,22 +101,25 @@ def main() -> None:
100
101
  )
101
102
  parser.add_argument("--no-deployments", dest="deployments", action="store_false")
102
103
  parser.add_argument(
103
- "--nb-try", default=20, type=int, help="Number of try to wait for the application to be ready"
104
+ "--nb-try",
105
+ default=20,
106
+ type=int,
107
+ help="Number of try to wait for the application to be ready",
104
108
  )
105
109
  parser.add_argument("--sleep", default=10, type=int, help="Sleep time before each try")
106
110
 
107
111
  args = parser.parse_args()
108
112
 
109
113
  if args.namespace:
110
- subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=default"], check=True)
114
+ subprocess.run(["kubectl", "config", "set-context", "--current", "--namespace=default"], check=True) # noqa: S607
111
115
 
112
116
  for _ in range(args.nb_try):
113
117
  time.sleep(args.sleep)
114
118
  success = True
115
119
  deployements_names = []
116
120
  if args.deployments:
117
- deployments = subprocess.run(
118
- ["kubectl", "get", "deployments", "--output=json"],
121
+ deployments = subprocess.run( # noqa: S603,S607,RUF100
122
+ ["kubectl", "get", "deployments", "--output=json"], # noqa: S607
119
123
  stdout=subprocess.PIPE,
120
124
  check=True,
121
125
  )
@@ -125,8 +129,8 @@ def main() -> None:
125
129
  deployment["metadata"]["name"] for deployment in json.loads(deployments.stdout)["items"]
126
130
  ]
127
131
 
128
- pods = subprocess.run(
129
- ["kubectl", "get", "pods", "--output=json", f"--selector={args.selector}"],
132
+ pods = subprocess.run( # noqa: S603,S607,RUF100
133
+ ["kubectl", "get", "pods", "--output=json", f"--selector={args.selector}"], # noqa: S607
130
134
  stdout=subprocess.PIPE,
131
135
  check=True,
132
136
  )
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env python3
2
+ # Copyright (c) 2020-2026, Camptocamp SA
2
3
 
3
4
  """The main function of some utilities."""
4
5
 
5
6
  import argparse
7
+ from importlib.metadata import version
6
8
 
7
- import pkg_resources
8
9
  import yaml
9
10
 
10
11
  import c2cciutils
@@ -22,8 +23,7 @@ def main() -> None:
22
23
  print(yaml.dump(c2cciutils.get_config(), default_flow_style=False, Dumper=yaml.SafeDumper))
23
24
 
24
25
  if args.version:
25
- version = pkg_resources.get_distribution("c2cciutils").version
26
- print(f"c2cciutils {version}")
26
+ print(f"c2cciutils {version('c2cciutils')}")
27
27
 
28
28
 
29
29
  if __name__ == "__main__":
@@ -0,0 +1,111 @@
1
+ [tool.ruff]
2
+ target-version = "py311"
3
+ line-length = 110
4
+
5
+ [tool.ruff.lint]
6
+ select = []
7
+ ignore = ["ANN401", "C90", "COM812", "D100", "D104", "D105", "D107", "D200", "D202", "D207", "D208", "D212", "E501", "EM101", "EM102", "ERA001", "FA", "FBT001", "FBT002", "FIX002", "I001", "PERF203", "PLR09", "PLR2004", "Q000", "S101", "SIM105", "T201", "TD002", "W293"]
8
+ extend-select = ["UP", "I", "S", "B", "ALL"]
9
+
10
+ [tool.ruff.lint.pydocstyle]
11
+ convention = "numpy"
12
+
13
+ [tool.ruff.lint.extend-per-file-ignores]
14
+ "tests/**" = ["ANN", "ARG", "ASYNC", "BLE001", "D", "DTZ", "E722", "FBT003", "INP001", "N", "PGH003", "PLC", "PLR2004", "PLW0603", "PLW1641", "RET", "RUF012", "RUF100", "S104", "S105", "S106", "S108", "S113", "S324", "S603", "S607", "S608", "SLF", "TRY003", "TRY301"]
15
+ "**/tests/**" = ["ANN", "ARG", "ASYNC", "BLE001", "D", "DTZ", "E722", "FBT003", "INP001", "N", "PGH003", "PLC", "PLR2004", "PLW0603", "PLW1641", "RET", "RUF012", "RUF100", "S104", "S105", "S106", "S108", "S113", "S324", "S603", "S607", "S608", "SLF", "TRY003", "TRY301"]
16
+ "**/test_*.py" = ["ANN", "ARG", "ASYNC", "BLE001", "D", "DTZ", "E722", "FBT003", "INP001", "N", "PGH003", "PLC", "PLR2004", "PLW0603", "PLW1641", "RET", "RUF012", "RUF100", "S104", "S105", "S106", "S108", "S113", "S324", "S603", "S607", "S608", "SLF", "TRY003", "TRY301"]
17
+ "**/*_test.py" = ["ANN", "ARG", "ASYNC", "BLE001", "D", "DTZ", "E722", "FBT003", "INP001", "N", "PGH003", "PLC", "PLR2004", "PLW0603", "PLW1641", "RET", "RUF012", "RUF100", "S104", "S105", "S106", "S108", "S113", "S324", "S603", "S607", "S608", "SLF", "TRY003", "TRY301"]
18
+
19
+ [tool.poetry]
20
+ exclude = ["c2cciutils/node_modules/**/test"]
21
+
22
+ [tool.poetry.dependencies]
23
+ python = ">=3.11,<4.0"
24
+ requests = "2.33.0"
25
+ "ruamel.yaml" = "0.19.1"
26
+ PyYAML = "6.0.3"
27
+ applications-download = "1.5.0"
28
+
29
+ [tool.poetry.group.dev.dependencies]
30
+ prospector = { version = "1.19.1", extras = ["with-bandit", "with-mypy", "with-pyroma", "with-ruff", "with_ruff", "with_pyroma"] }
31
+ prospector-profile-duplicated = "1.12.0"
32
+ prospector-profile-utils = "1.27.0"
33
+ ruff = "0.16.9"
34
+ types-requests = "2.33.0.20260906"
35
+ types-pyyaml = "6.0.12.20260906"
36
+ pylint = "4.0.9"
37
+
38
+ [tool.poetry-dynamic-versioning]
39
+ enable = false
40
+ vcs = "git"
41
+ pattern = "^(?P<base>\\d+(\\.\\d+)*)"
42
+ format-jinja = """
43
+ {%- if env.get("VERSION_TYPE") == "default_branch" -%}
44
+ {{serialize_pep440(bump_version(base, 1), dev=distance)}}
45
+ {%- elif env.get("VERSION_TYPE") == "stabilization_branch" -%}
46
+ {{serialize_pep440(bump_version(base, 2), dev=distance)}}
47
+ {%- elif distance == 0 -%}
48
+ {{serialize_pep440(base)}}
49
+ {%- else -%}
50
+ {{serialize_pep440(bump_version(base), dev=distance)}}
51
+ {%- endif -%}
52
+ """
53
+
54
+ [tool.poetry-plugin-tweak-dependencies-version]
55
+ default = "major"
56
+ codespell = "full"
57
+ cryptography = "present"
58
+ certifi = "present"
59
+
60
+ [project]
61
+ classifiers = [
62
+ 'Development Status :: 5 - Production/Stable',
63
+ 'Environment :: Console',
64
+ 'Intended Audience :: Developers',
65
+ 'Operating System :: OS Independent',
66
+ 'Programming Language :: Python',
67
+ 'Programming Language :: Python :: 3',
68
+ 'Programming Language :: Python :: 3.11',
69
+ 'Programming Language :: Python :: 3.12',
70
+ 'Programming Language :: Python :: 3.13',
71
+ 'Typing :: Typed',
72
+ ]
73
+ dynamic = ["dependencies"]
74
+ name = "c2cciutils"
75
+ description = "Common utilities for Camptocamp CI"
76
+ readme = "README.md"
77
+ keywords = ["ci"]
78
+ license = "FreeBSD"
79
+ authors = [{name = "Camptocamp",email = "info@camptocamp.com"}]
80
+ packages = [{ include = "c2cciutils" }]
81
+ include = [
82
+ "c2cciutils/py.typed",
83
+ "c2cciutils/*.graphql",
84
+ "c2cciutils/*.yaml",
85
+ "c2cciutils/*.json",
86
+ ]
87
+ requires-python = ">=3.11"
88
+ dependencies = ["requests<3,>=2", "ruamel.yaml<1,>=0", "PyYAML<7,>=6", "applications-download<2,>=1"]
89
+ version = "1.9.0.dev29"
90
+
91
+ [project.urls]
92
+ repository = "https://github.com/camptocamp/c2cciutils"
93
+ "Bug Tracker" = "https://github.com/camptocamp/c2cciutils/issues"
94
+
95
+ [project.scripts]
96
+ c2cciutils = "c2cciutils.scripts.main:main"
97
+ c2cciutils-env = "c2cciutils.scripts.env:main"
98
+ c2cciutils-k8s-install = "c2cciutils.scripts.k8s.install:main"
99
+ c2cciutils-k8s-db = "c2cciutils.scripts.k8s.db:main"
100
+ c2cciutils-k8s-wait = "c2cciutils.scripts.k8s.wait:main"
101
+ c2cciutils-k8s-logs = "c2cciutils.scripts.k8s.logs:main"
102
+ c2cciutils-docker-logs = "c2cciutils.scripts.docker_logs:main"
103
+
104
+ [build-system]
105
+ requires = [
106
+ "poetry-core>=1.0.0",
107
+ "poetry-dynamic-versioning[plugin]>=0.19.0",
108
+ "poetry-dynamic-versioning",
109
+ # "poetry-plugin-drop-python-upper-constraint",
110
+ ]
111
+ build-backend = "poetry.core.masonry.api"
@@ -1,3 +0,0 @@
1
- # https://docs.renovatebot.com/modules/datasource/#github-releases-datasource
2
- k3d-io/k3d: v5.8.3 # github-releases
3
- postgresql: 16.4.14 # helm - https://charts.bitnami.com/bitnami
@@ -1,22 +0,0 @@
1
- # yaml-language-server: $schema=https://raw.githubusercontent.com/camptocamp/c2cciutils/master/c2cciutils/schema-applications.json
2
-
3
- k3d-io/k3d:
4
- get-file-name: k3d-linux-amd64
5
- to-file-name: k3d
6
- finish-commands:
7
- - - chmod
8
- - +x
9
- - k3d
10
- - - k3d
11
- - --version
12
- helm/chart-releaser:
13
- get-file-name: chart-releaser_{short_version}_linux_amd64.tar.gz
14
- type: tar
15
- tar-file-name: cr
16
- to-file-name: cr
17
- finish-commands:
18
- - - chmod
19
- - +x
20
- - cr
21
- - - cr
22
- - version
@@ -1,51 +0,0 @@
1
- """Automatically generated file from a JSON schema."""
2
-
3
- from typing import Literal, TypedDict
4
-
5
- # | Application configuration.
6
- # |
7
- # | An application configuration
8
- ApplicationConfiguration = TypedDict(
9
- "ApplicationConfiguration",
10
- {
11
- # | URL pattern.
12
- # |
13
- # | URL pattern, to be used for files that didn't come from GitHub release, available arguments: {version}
14
- "url-pattern": str,
15
- # | The type of file.
16
- # |
17
- # | The type of file
18
- "type": "TheTypeOfFile",
19
- # | The filename to get.
20
- # |
21
- # | The name of the file to get in the GitHub release
22
- "get-file-name": str,
23
- # | The created tile name.
24
- # |
25
- # | The name of the final tile we will create
26
- "to-file-name": str,
27
- # | The tile name to get in the tar file.
28
- "tar-file-name": str,
29
- # | The commands to run after the tile creation.
30
- "finish-commands": list[list[str]],
31
- },
32
- total=False,
33
- )
34
-
35
-
36
- ApplicationsConfiguration = dict[str, "ApplicationConfiguration"]
37
- """
38
- Applications configuration.
39
-
40
- All the applications configuration
41
- """
42
-
43
-
44
- TheTypeOfFile = Literal["tar"]
45
- """
46
- The type of file.
47
-
48
- The type of file
49
- """
50
- THETYPEOFFILE_TAR: Literal["tar"] = "tar"
51
- """The values for the 'The type of file' enum"""
@@ -1 +0,0 @@
1
- """The scripts main functions."""
@@ -1,108 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import argparse
4
- import os
5
- import subprocess # nosec
6
- import tarfile
7
- import urllib
8
- from glob import glob
9
- from io import BytesIO
10
- from typing import Optional, cast
11
-
12
- import requests
13
- import yaml
14
-
15
- from c2cciutils import applications_definition
16
-
17
-
18
- def main() -> None:
19
- """Download applications from GitHub releases or any other URLs to the ~/.local/bin folder."""
20
- argparser = argparse.ArgumentParser(
21
- description="""Download applications from GitHub releases or any other URLs to the ~/.local/bin folder.
22
- Based on tow files, the first contains the information about from where to download the applications,
23
- how to extract the application from the archive, and the executable name.
24
- The second file contains the versions of the applications to download,
25
- this file is usually updated by Renovate."""
26
- )
27
- argparser.add_argument("--applications-file", required=True)
28
- argparser.add_argument("--versions-file", required=True)
29
- args = argparser.parse_args()
30
-
31
- with open(args.versions_file, encoding="utf-8") as config_file:
32
- versions = cast(dict[str, str], yaml.load(config_file, Loader=yaml.SafeLoader))
33
- with open(args.applications_file, encoding="utf-8") as config_file:
34
- applications = cast(
35
- applications_definition.ApplicationsConfiguration, yaml.load(config_file, Loader=yaml.SafeLoader)
36
- )
37
- download_applications(applications, versions)
38
-
39
-
40
- def download_c2cciutils_applications(name: Optional[str] = None) -> None:
41
- """Download the applications defined in the c2cciutils package."""
42
- with open(
43
- os.path.join(os.path.dirname(os.path.dirname(__file__)), "applications-versions.yaml"),
44
- encoding="utf-8",
45
- ) as config_file:
46
- versions = cast(dict[str, str], yaml.load(config_file, Loader=yaml.SafeLoader))
47
- with open(
48
- os.path.join(os.path.dirname(os.path.dirname(__file__)), "applications.yaml"), encoding="utf-8"
49
- ) as config_file:
50
- applications = cast(
51
- applications_definition.ApplicationsConfiguration, yaml.load(config_file, Loader=yaml.SafeLoader)
52
- )
53
- if name is not None:
54
- applications = {name: applications[name]}
55
- download_applications(applications, versions)
56
-
57
-
58
- def download_applications(
59
- applications: applications_definition.ApplicationsConfiguration, versions: dict[str, str]
60
- ) -> None:
61
- """Download the versions of applications specified in the configuration."""
62
- bin_path = os.path.join(os.environ["HOME"], ".local", "bin")
63
- if not os.path.exists(bin_path):
64
- os.makedirs(bin_path)
65
-
66
- for key, app in applications.items():
67
- # The versions file is used to don't re-download an already downloaded application
68
- version_file = os.path.join(bin_path, f"{app['to-file-name']}-version-{versions[key]}")
69
- if not os.path.exists(version_file):
70
- print(f"Download {app['to-file-name']} version {versions[key]}")
71
- version = versions[key]
72
- version_quote = urllib.parse.quote_plus(version)
73
- params = {
74
- "version": version,
75
- "version_quote": version_quote,
76
- "short_version": version.lstrip("v"),
77
- }
78
- response = requests.get(
79
- app.get(
80
- "url-pattern",
81
- f"https://github.com/{key}/releases/download/{version_quote}/{app.get('get-file-name', '')}",
82
- ).format(**params),
83
- timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")),
84
- )
85
- response.raise_for_status()
86
-
87
- if app.get("type") == "tar":
88
- with tarfile.open(fileobj=BytesIO(response.content)) as tar:
89
- extracted_file = tar.extractfile(app["tar-file-name"])
90
- assert extracted_file is not None
91
- content = extracted_file.read()
92
- else:
93
- content = response.content
94
-
95
- with open(os.path.join(bin_path, app["to-file-name"]), "wb") as destination_file:
96
- destination_file.write(content)
97
-
98
- for command in app.get("finish-commands", []):
99
- subprocess.run(command, check=True, cwd=bin_path)
100
-
101
- for file_name in glob(f"{app['to-file-name']}-version-*"):
102
- os.remove(file_name)
103
- with open(version_file, "w", encoding="utf-8") as _:
104
- pass
105
-
106
-
107
- if __name__ == "__main__":
108
- main()
@@ -1,97 +0,0 @@
1
- [tool.black]
2
- line-length = 110
3
- target-version = ["py39"]
4
-
5
- [tool.isort]
6
- multi_line_output = 3
7
- include_trailing_comma = true
8
- force_grid_wrap = false
9
- use_parentheses = true
10
- line_length = 110
11
-
12
- [tool.mypy]
13
- python_version = "3.11"
14
- ignore_missing_imports = true
15
- warn_redundant_casts = true
16
- warn_unused_ignores = true
17
- warn_return_any = true
18
- strict = true
19
-
20
- [tool.poetry]
21
- name = "c2cciutils"
22
- version = "1.8.0.dev68"
23
- description = "Common utilities for Camptocamp CI"
24
- readme = "README.md"
25
- authors = ["Camptocamp <info@camptocamp.com>"]
26
- keywords = ["ci"]
27
- repository = "https://github.com/camptocamp/c2cciutils"
28
- license = "FreeBSD"
29
- packages = [{ include = "c2cciutils" }]
30
- classifiers = [
31
- "Development Status :: 5 - Production/Stable",
32
- "Environment :: Console",
33
- "Operating System :: OS Independent",
34
- "Programming Language :: Python",
35
- "Intended Audience :: Developers",
36
- "License :: OSI Approved :: MIT License",
37
- "Typing :: Typed",
38
- ]
39
- include = [
40
- "c2cciutils/py.typed",
41
- "c2cciutils/*.graphql",
42
- "c2cciutils/*.yaml",
43
- "c2cciutils/*.json",
44
- ]
45
- exclude = ["c2cciutils/node_modules/**/test"]
46
-
47
- [tool.poetry.scripts]
48
- c2cciutils = "c2cciutils.scripts.main:main"
49
- c2cciutils-env = "c2cciutils.scripts.env:main"
50
- c2cciutils-k8s-install = "c2cciutils.scripts.k8s.install:main"
51
- c2cciutils-k8s-db = "c2cciutils.scripts.k8s.db:main"
52
- c2cciutils-k8s-wait = "c2cciutils.scripts.k8s.wait:main"
53
- c2cciutils-k8s-logs = "c2cciutils.scripts.k8s.logs:main"
54
- c2cciutils-docker-logs = "c2cciutils.scripts.docker_logs:main"
55
-
56
- [tool.poetry.dependencies]
57
- python = ">=3.9,<4.0"
58
- requests = "2.32.3"
59
- "ruamel.yaml" = "0.18.10"
60
- PyYAML = "6.0.2"
61
-
62
- [tool.poetry.group.dev.dependencies]
63
- prospector = { version = "1.14.1", extras = ["with-bandit", "with-mypy", "with-pyroma", "with-ruff"] }
64
- prospector-profile-duplicated = "1.10.4"
65
- prospector-profile-utils = "1.17.0"
66
- types-requests = "2.32.0.20250301"
67
- types-pyyaml = "6.0.12.20241230"
68
-
69
- [build-system]
70
- requires = [
71
- "poetry-core>=1.0.0",
72
- "poetry-dynamic-versioning[plugin]>=0.19.0",
73
- "poetry-plugin-tweak-dependencies-version",
74
- "poetry-dynamic-versioning",
75
- # "poetry-plugin-drop-python-upper-constraint",
76
- ]
77
- build-backend = "poetry.core.masonry.api"
78
-
79
- [tool.poetry-dynamic-versioning]
80
- enable = false
81
- vcs = "git"
82
- pattern = "^(?P<base>\\d+(\\.\\d+)*)"
83
- format-jinja = """
84
- {%- if env.get("VERSION_TYPE") == "version_branch" -%}
85
- {{serialize_pep440(bump_version(base, 1 if env.get("IS_MASTER") == "TRUE" else 2), dev=distance)}}
86
- {%- elif distance == 0 -%}
87
- {{serialize_pep440(base)}}
88
- {%- else -%}
89
- {{serialize_pep440(bump_version(base), dev=distance)}}
90
- {%- endif -%}
91
- """
92
-
93
- [tool.poetry-plugin-tweak-dependencies-version]
94
- default = "major"
95
- codespell = "full"
96
- cryptography = "present"
97
- certifi = "present"