qubership-pipelines-common-library 0.1.4__tar.gz → 0.1.6__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.
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/PKG-INFO +8 -1
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/README.md +8 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/pyproject.toml +1 -1
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/git_client.py +2 -1
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/github_client.py +95 -16
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/utils_string.py +5 -1
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/LICENSE +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/__init__.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/__init__.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/artifactory_client.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/execution/__init__.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/execution/exec_command.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/execution/exec_context.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/execution/exec_context_file.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/execution/exec_info.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/execution/exec_logger.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/gitlab_client.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/jenkins_client.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/kube_client.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/log_client.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/minio_client.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/__init__.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/rest.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/utils.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/utils_context.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/utils_dictionary.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/utils_file.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/utils/utils_json.py +0 -0
- {qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/qubership_pipelines_common_library/v1/webex_client.py +0 -0
{qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: qubership-pipelines-common-library
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Qubership Pipelines common library
|
|
5
5
|
Author: Igor Lebedev
|
|
6
6
|
Author-email: lebedev.light@gmail.com
|
|
@@ -34,6 +34,8 @@ Library provides easy-to-use clients and wrappers for common devops services (e.
|
|
|
34
34
|
|
|
35
35
|
Library is presented as a set of clients with predefined operations
|
|
36
36
|
|
|
37
|
+
Auto-generated reference (via mkdocs) is available on [this repo's GitHub Pages](https://netcracker.github.io/qubership-pipelines-common-python-library)
|
|
38
|
+
|
|
37
39
|
## Installation
|
|
38
40
|
|
|
39
41
|
- Add the following section to your dependencies to add Qubership library as a dependency in your project:
|
|
@@ -58,3 +60,8 @@ You can install it via `pip`:
|
|
|
58
60
|
pip install qubership-pipelines-common-library-py39
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
## Sample implementation
|
|
64
|
+
|
|
65
|
+
Sample implementation of CLI commands using this library is available at [Quber CLI](https://github.com/LightlessOne/Quber-CLI?tab=readme-ov-file)
|
|
66
|
+
|
|
67
|
+
It includes reference python implementation along with [Development Guide](https://github.com/LightlessOne/Quber-CLI/blob/master/docs/development.md)
|
{qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/README.md
RENAMED
|
@@ -8,6 +8,8 @@ Library provides easy-to-use clients and wrappers for common devops services (e.
|
|
|
8
8
|
|
|
9
9
|
Library is presented as a set of clients with predefined operations
|
|
10
10
|
|
|
11
|
+
Auto-generated reference (via mkdocs) is available on [this repo's GitHub Pages](https://netcracker.github.io/qubership-pipelines-common-python-library)
|
|
12
|
+
|
|
11
13
|
## Installation
|
|
12
14
|
|
|
13
15
|
- Add the following section to your dependencies to add Qubership library as a dependency in your project:
|
|
@@ -31,3 +33,9 @@ You can install it via `pip`:
|
|
|
31
33
|
```bash
|
|
32
34
|
pip install qubership-pipelines-common-library-py39
|
|
33
35
|
```
|
|
36
|
+
|
|
37
|
+
## Sample implementation
|
|
38
|
+
|
|
39
|
+
Sample implementation of CLI commands using this library is available at [Quber CLI](https://github.com/LightlessOne/Quber-CLI?tab=readme-ov-file)
|
|
40
|
+
|
|
41
|
+
It includes reference python implementation along with [Development Guide](https://github.com/LightlessOne/Quber-CLI/blob/master/docs/development.md)
|
|
@@ -39,7 +39,7 @@ class GitClient:
|
|
|
39
39
|
self.branch = None # last processed branch
|
|
40
40
|
logging.info("Git Client configured for %s", self.host)
|
|
41
41
|
|
|
42
|
-
def clone(self, repo_path: str, branch: str, temp_path: str):
|
|
42
|
+
def clone(self, repo_path: str, branch: str, temp_path: str, **kwargs):
|
|
43
43
|
""""""
|
|
44
44
|
repo_path = repo_path.lstrip("/").rstrip("/")
|
|
45
45
|
if not repo_path:
|
|
@@ -56,6 +56,7 @@ class GitClient:
|
|
|
56
56
|
self._gen_repo_auth_url(self.host, self.username, self.password, self.repo_path),
|
|
57
57
|
temp_path,
|
|
58
58
|
branch=branch,
|
|
59
|
+
**kwargs
|
|
59
60
|
)
|
|
60
61
|
|
|
61
62
|
def commit_and_push(self, commit_message: str):
|
|
@@ -12,13 +12,21 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import json
|
|
15
16
|
import logging
|
|
17
|
+
import tempfile
|
|
18
|
+
import uuid
|
|
19
|
+
import zipfile
|
|
16
20
|
import requests
|
|
17
21
|
|
|
18
22
|
from datetime import datetime, timezone
|
|
19
23
|
from pathlib import Path
|
|
20
24
|
from github import Github, Auth
|
|
21
25
|
from time import sleep
|
|
26
|
+
from github.Artifact import Artifact
|
|
27
|
+
from github.PaginatedList import PaginatedList
|
|
28
|
+
from github.WorkflowRun import WorkflowRun
|
|
29
|
+
|
|
22
30
|
from qubership_pipelines_common_library.v1.execution.exec_info import ExecutionInfo
|
|
23
31
|
|
|
24
32
|
|
|
@@ -39,6 +47,10 @@ class GithubClient:
|
|
|
39
47
|
CONCLUSION_ACTION_REQUIRED = "action_required"
|
|
40
48
|
|
|
41
49
|
BREAK_STATUS_LIST = [STATUS_COMPLETED, STATUS_FAILURE]
|
|
50
|
+
DISPATCH_PARAMS_LIMIT = 10
|
|
51
|
+
DEFAULT_UUID_ARTIFACT_NAME = "input_params"
|
|
52
|
+
DEFAULT_UUID_FILE_NAME = "input_params.json"
|
|
53
|
+
DEFAULT_UUID_PARAM_NAME = "workflow_run_uuid"
|
|
42
54
|
|
|
43
55
|
def __init__(self, token: str = None, api_url: str = None, **kwargs):
|
|
44
56
|
"""
|
|
@@ -55,9 +67,23 @@ class GithubClient:
|
|
|
55
67
|
logging.info("Github Client configured")
|
|
56
68
|
|
|
57
69
|
def trigger_workflow(self, owner: str, repo_name: str, workflow_file_name: str, branch: str, pipeline_params: dict,
|
|
58
|
-
timeout_seconds: float = 30.0, wait_seconds: float = 3.0
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
timeout_seconds: float = 30.0, wait_seconds: float = 3.0, find_via_uuid: bool = False,
|
|
71
|
+
uuid_param_name: str = DEFAULT_UUID_PARAM_NAME,
|
|
72
|
+
uuid_artifact_name: str = DEFAULT_UUID_ARTIFACT_NAME, uuid_file_name: str = DEFAULT_UUID_FILE_NAME):
|
|
73
|
+
""" There's currently no reliable way to get ID of triggered workflow, without adding explicit ID as an input parameter to each workflow, dispatch is async and doesn't return anything
|
|
74
|
+
This method supports two different ways to find and return started workflow:
|
|
75
|
+
Unreliable - where we start looking for newly created runs of that workflow, filtering them as much as possible (might return wrong run in a concurrent scenario)
|
|
76
|
+
Reliable:
|
|
77
|
+
you need to add specific explicit ID param to the workflow you are triggering (e.g. 'workflow_run_uuid'),
|
|
78
|
+
said workflow should have a step where it will save its input params,
|
|
79
|
+
and then you run this method with 'find_via_uuid = True'
|
|
80
|
+
"""
|
|
81
|
+
if pipeline_params is None:
|
|
82
|
+
pipeline_params = {}
|
|
83
|
+
if find_via_uuid:
|
|
84
|
+
pipeline_params[uuid_param_name] = str(uuid.uuid4())
|
|
85
|
+
if len(pipeline_params) > GithubClient.DISPATCH_PARAMS_LIMIT:
|
|
86
|
+
logging.warning(f"Trying to dispatch workflow with more than {GithubClient.DISPATCH_PARAMS_LIMIT} pipeline_params, GitHub does not support it!")
|
|
61
87
|
workflow = self.gh.get_repo(f"{owner}/{repo_name}", lazy=True).get_workflow(workflow_file_name)
|
|
62
88
|
dispatch_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
63
89
|
execution = ExecutionInfo()
|
|
@@ -65,14 +91,22 @@ class GithubClient:
|
|
|
65
91
|
logging.info(f"Workflow Dispatch event for {workflow_file_name} is sent, workflow is created: {is_created}")
|
|
66
92
|
if is_created:
|
|
67
93
|
current_timeout = 0
|
|
94
|
+
already_checked_runs = []
|
|
68
95
|
while current_timeout < timeout_seconds:
|
|
69
96
|
runs_list = workflow.get_runs(event="workflow_dispatch", created=f">={dispatch_time}", branch=branch)
|
|
70
97
|
if runs_list.totalCount > 0:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
if find_via_uuid:
|
|
99
|
+
created_run = self._find_run_via_uuid_input_param(runs_list, already_checked_runs,
|
|
100
|
+
uuid_artifact_name, uuid_file_name,
|
|
101
|
+
uuid_param_name,
|
|
102
|
+
pipeline_params[uuid_param_name])
|
|
103
|
+
else:
|
|
104
|
+
created_run = runs_list.get_page(0).pop()
|
|
105
|
+
if created_run:
|
|
106
|
+
logging.info(f"Pipeline successfully started at {created_run.html_url}")
|
|
107
|
+
return execution.with_name(created_run.name).with_id(created_run.id) \
|
|
108
|
+
.with_url(created_run.html_url).with_params(pipeline_params) \
|
|
109
|
+
.start()
|
|
76
110
|
current_timeout += wait_seconds
|
|
77
111
|
logging.info(f"Waiting for triggered workflow run to start... Timeout {wait_seconds} seconds")
|
|
78
112
|
sleep(wait_seconds)
|
|
@@ -143,15 +177,60 @@ class GithubClient:
|
|
|
143
177
|
local_dir_path.mkdir(parents=True, exist_ok=True)
|
|
144
178
|
run = self.gh.get_repo(repo_full_name).get_workflow_run(int(execution.get_id()))
|
|
145
179
|
for artifact in run.get_artifacts():
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
180
|
+
self._save_artifact_to_dir(artifact, local_dir_path)
|
|
181
|
+
|
|
182
|
+
def get_workflow_run_input_params(self, run: WorkflowRun, artifact_name: str = DEFAULT_UUID_ARTIFACT_NAME,
|
|
183
|
+
file_name: str = DEFAULT_UUID_FILE_NAME):
|
|
184
|
+
""""""
|
|
185
|
+
for artifact in run.get_artifacts():
|
|
186
|
+
if artifact.name == artifact_name:
|
|
187
|
+
return self._get_input_params_from_artifact(artifact, file_name)
|
|
188
|
+
logging.info(f"Could not find input_params artifact for run {run.id}")
|
|
189
|
+
return {}
|
|
190
|
+
|
|
191
|
+
def _find_run_via_uuid_input_param(self, runs_list: PaginatedList[WorkflowRun], already_checked_runs: list,
|
|
192
|
+
uuid_artifact_name: str, uuid_file_name: str,
|
|
193
|
+
uuid_param_name: str, uuid_param_value: str):
|
|
194
|
+
for run in runs_list:
|
|
195
|
+
if run.id in already_checked_runs:
|
|
150
196
|
continue
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
197
|
+
for artifact in run.get_artifacts():
|
|
198
|
+
if artifact.name == uuid_artifact_name:
|
|
199
|
+
if self._check_input_params_uuid(artifact, uuid_file_name, uuid_param_name, uuid_param_value):
|
|
200
|
+
logging.info(f"Found workflow run with expected UUID: {run.id} with {uuid_param_name}={uuid_param_value}")
|
|
201
|
+
return run
|
|
202
|
+
else:
|
|
203
|
+
already_checked_runs.append(run.id)
|
|
204
|
+
break
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def _check_input_params_uuid(self, artifact: Artifact, uuid_file_name: str, uuid_param_name: str, uuid_param_value: str):
|
|
208
|
+
try:
|
|
209
|
+
input_params = self._get_input_params_from_artifact(artifact, uuid_file_name)
|
|
210
|
+
return input_params.get(uuid_param_name) == uuid_param_value
|
|
211
|
+
except Exception as ex:
|
|
212
|
+
logging.error(f"Exception when downloading and checking artifact ({artifact.name}): {ex}")
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
def _get_input_params_from_artifact(self, artifact: Artifact, file_name: str):
|
|
216
|
+
with tempfile.TemporaryDirectory() as temp_dirname:
|
|
217
|
+
artifact_path = self._save_artifact_to_dir(artifact, temp_dirname)
|
|
218
|
+
with zipfile.ZipFile(artifact_path) as zf:
|
|
219
|
+
zf.extractall(temp_dirname)
|
|
220
|
+
with open(Path(temp_dirname, file_name)) as input_params_file:
|
|
221
|
+
return json.load(input_params_file)
|
|
222
|
+
|
|
223
|
+
def _save_artifact_to_dir(self, artifact: Artifact, dirname):
|
|
224
|
+
local_path = Path(dirname, f"{artifact.name}.zip")
|
|
225
|
+
(status, headers, _) = artifact.requester.requestBlob("GET", artifact.archive_download_url)
|
|
226
|
+
if status != 302:
|
|
227
|
+
logging.error(f"Unexpected status while downloading run artifact {artifact.name}: expected 302, got {status}")
|
|
228
|
+
return None
|
|
229
|
+
response = requests.get(headers["location"])
|
|
230
|
+
with local_path.open('wb') as f:
|
|
231
|
+
logging.info(f"saving {local_path}...")
|
|
232
|
+
f.write(response.content)
|
|
233
|
+
return local_path
|
|
155
234
|
|
|
156
235
|
def _map_status_and_conclusion(self, status: str, conclusion: str, default_status: str):
|
|
157
236
|
logging.info(f"status: {status}, conclusion: {conclusion}")
|
|
@@ -21,4 +21,8 @@ class UtilsString:
|
|
|
21
21
|
# split by newline, comma, or semicolon
|
|
22
22
|
parts = re.split(r'[\n,;]', input_str)
|
|
23
23
|
# trim whitespace from each part
|
|
24
|
-
return [part.strip() for part in parts if part.strip()]
|
|
24
|
+
return [part.strip() for part in parts if part.strip()]
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def convert_to_bool(input_str):
|
|
28
|
+
return str(input_str).strip().lower() == "true"
|
{qubership_pipelines_common_library-0.1.4 → qubership_pipelines_common_library-0.1.6}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|