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.
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/LICENSE +1 -1
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/PKG-INFO +9 -10
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/__init__.py +28 -19
- c2cciutils-1.9.0.dev29/c2cciutils/applications-versions.yaml +2 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/configuration.py +22 -18
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/env.py +9 -6
- c2cciutils-1.9.0.dev29/c2cciutils/scripts/__init__.py +3 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/docker_logs.py +17 -13
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/env.py +1 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/__init__.py +2 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/db.py +16 -15
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/install.py +6 -3
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/logs.py +13 -12
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/k8s/wait.py +16 -12
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/scripts/main.py +3 -3
- c2cciutils-1.9.0.dev29/pyproject.toml +111 -0
- c2cciutils-1.8.0.dev68/c2cciutils/applications-versions.yaml +0 -3
- c2cciutils-1.8.0.dev68/c2cciutils/applications.yaml +0 -22
- c2cciutils-1.8.0.dev68/c2cciutils/applications_definition.py +0 -51
- c2cciutils-1.8.0.dev68/c2cciutils/scripts/__init__.py +0 -1
- c2cciutils-1.8.0.dev68/c2cciutils/scripts/download_applications.py +0 -108
- c2cciutils-1.8.0.dev68/pyproject.toml +0 -97
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/README.md +0 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/branches.graphql +0 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/commits.graphql +0 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/default_branch.graphql +0 -0
- {c2cciutils-1.8.0.dev68 → c2cciutils-1.9.0.dev29}/c2cciutils/schema.json +0 -0
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: c2cciutils
|
|
3
|
-
Version: 1.
|
|
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.
|
|
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 (
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
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
|
|
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",
|
|
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
|
-
|
|
54
|
-
|
|
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:
|
|
65
|
-
line:
|
|
66
|
-
col:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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"])
|
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
|
|
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
|
|
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::")
|
|
@@ -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
|
|
5
|
+
import datetime
|
|
5
6
|
import subprocess # nosec
|
|
6
7
|
import sys
|
|
7
|
-
from
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
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("
|
|
38
|
-
timestamp_file.write(datetime.
|
|
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 }}"],
|
|
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"
|
|
53
|
+
subprocess.run(["docker", "logs", *timestamp_args, name], check=False) # noqa: S603,S607
|
|
50
54
|
_print("::endgroup::")
|
|
51
55
|
|
|
52
56
|
|
|
@@ -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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
|
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}")
|
|
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'])}"
|
|
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",
|
|
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
|
-
|
|
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,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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|