qubership-pipelines-common-library 0.2.6__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qubership_pipelines_common_library/__init__.py +1 -1
- qubership_pipelines_common_library/v1/artifactory_client.py +1 -1
- qubership_pipelines_common_library/v1/execution/exec_command.py +63 -2
- qubership_pipelines_common_library/v1/execution/exec_context.py +6 -6
- qubership_pipelines_common_library/v1/execution/exec_context_file.py +1 -1
- qubership_pipelines_common_library/v1/execution/exec_info.py +4 -0
- qubership_pipelines_common_library/v1/execution/exec_logger.py +7 -5
- qubership_pipelines_common_library/v1/github_client.py +10 -1
- qubership_pipelines_common_library/v1/gitlab_client.py +175 -11
- qubership_pipelines_common_library/v1/jenkins_client.py +55 -18
- qubership_pipelines_common_library/v1/maven_client.py +2 -2
- qubership_pipelines_common_library/v1/minio_client.py +1 -1
- qubership_pipelines_common_library/v1/utils/rest.py +1 -1
- qubership_pipelines_common_library/v1/utils/utils.py +1 -1
- qubership_pipelines_common_library/v1/utils/utils_cli.py +43 -9
- qubership_pipelines_common_library/v1/utils/utils_dictionary.py +1 -1
- qubership_pipelines_common_library/v1/utils/utils_file.py +17 -0
- qubership_pipelines_common_library/v1/utils/utils_logging.py +53 -0
- qubership_pipelines_common_library/v2/__init__.py +0 -0
- qubership_pipelines_common_library/v2/artifacts_finder/__init__.py +0 -0
- qubership_pipelines_common_library/v2/artifacts_finder/artifact_finder.py +56 -0
- qubership_pipelines_common_library/v2/artifacts_finder/auth/__init__.py +0 -0
- qubership_pipelines_common_library/v2/artifacts_finder/auth/aws_credentials.py +106 -0
- qubership_pipelines_common_library/v2/artifacts_finder/auth/azure_credentials.py +72 -0
- qubership_pipelines_common_library/v2/artifacts_finder/auth/gcp_credentials.py +88 -0
- qubership_pipelines_common_library/v2/artifacts_finder/model/__init__.py +0 -0
- qubership_pipelines_common_library/v2/artifacts_finder/model/artifact.py +20 -0
- qubership_pipelines_common_library/v2/artifacts_finder/model/artifact_provider.py +35 -0
- qubership_pipelines_common_library/v2/artifacts_finder/model/credentials.py +16 -0
- qubership_pipelines_common_library/v2/artifacts_finder/model/credentials_provider.py +16 -0
- qubership_pipelines_common_library/v2/artifacts_finder/providers/__init__.py +0 -0
- qubership_pipelines_common_library/v2/artifacts_finder/providers/artifactory.py +52 -0
- qubership_pipelines_common_library/v2/artifacts_finder/providers/aws_code_artifact.py +79 -0
- qubership_pipelines_common_library/v2/artifacts_finder/providers/azure_artifacts.py +98 -0
- qubership_pipelines_common_library/v2/artifacts_finder/providers/gcp_artifact_registry.py +50 -0
- qubership_pipelines_common_library/v2/artifacts_finder/providers/nexus.py +41 -0
- qubership_pipelines_common_library/v2/extensions/pipeline_data_importer.py +24 -0
- qubership_pipelines_common_library/v2/github/__init__.py +0 -0
- qubership_pipelines_common_library/v2/github/github_client.py +5 -0
- qubership_pipelines_common_library/v2/github/github_pipeline_data_importer.py +21 -0
- qubership_pipelines_common_library/v2/github/github_run_pipeline_command.py +175 -0
- qubership_pipelines_common_library/v2/github/safe_github_client.py +24 -0
- qubership_pipelines_common_library/v2/gitlab/__init__.py +0 -0
- qubership_pipelines_common_library/v2/gitlab/custom_extensions.py +101 -0
- qubership_pipelines_common_library/v2/gitlab/gitlab_client.py +36 -0
- qubership_pipelines_common_library/v2/gitlab/gitlab_pipeline_data_importer.py +26 -0
- qubership_pipelines_common_library/v2/gitlab/gitlab_run_pipeline_command.py +195 -0
- qubership_pipelines_common_library/v2/gitlab/safe_gitlab_client.py +32 -0
- qubership_pipelines_common_library/v2/jenkins/__init__.py +0 -0
- qubership_pipelines_common_library/v2/jenkins/custom_extensions.py +63 -0
- qubership_pipelines_common_library/v2/jenkins/jenkins_client.py +5 -0
- qubership_pipelines_common_library/v2/jenkins/jenkins_pipeline_data_importer.py +31 -0
- qubership_pipelines_common_library/v2/jenkins/jenkins_run_pipeline_command.py +165 -0
- qubership_pipelines_common_library/v2/jenkins/safe_jenkins_client.py +14 -0
- qubership_pipelines_common_library/v2/podman/__init__.py +0 -0
- qubership_pipelines_common_library/v2/podman/podman_command.md +178 -0
- qubership_pipelines_common_library/v2/podman/podman_command.py +311 -0
- qubership_pipelines_common_library/v2/sops/sops_client.py +116 -0
- qubership_pipelines_common_library/v2/utils/crypto_utils.py +48 -0
- qubership_pipelines_common_library/v2/utils/extension_utils.py +22 -0
- qubership_pipelines_common_library/v2/utils/retry_decorator.py +93 -0
- {qubership_pipelines_common_library-0.2.6.dist-info → qubership_pipelines_common_library-2.0.1.dist-info}/METADATA +5 -3
- qubership_pipelines_common_library-2.0.1.dist-info/RECORD +76 -0
- qubership_pipelines_common_library-0.2.6.dist-info/RECORD +0 -32
- {qubership_pipelines_common_library-0.2.6.dist-info → qubership_pipelines_common_library-2.0.1.dist-info}/WHEEL +0 -0
- {qubership_pipelines_common_library-0.2.6.dist-info → qubership_pipelines_common_library-2.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,7 +22,7 @@ class Artifact:
|
|
|
22
22
|
def from_string(artifact_str: str):
|
|
23
23
|
parts = artifact_str.split(":")
|
|
24
24
|
if len(parts) == 3:
|
|
25
|
-
|
|
25
|
+
artifact, version = parts[1], parts[-1]
|
|
26
26
|
return Artifact(artifact, version)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -67,7 +67,7 @@ class MavenArtifactSearcher:
|
|
|
67
67
|
if not artifact:
|
|
68
68
|
artifact = Artifact(artifact_id=artifact_id, version=version, extension=extension)
|
|
69
69
|
if not artifact.artifact_id or not artifact.version:
|
|
70
|
-
raise Exception(
|
|
70
|
+
raise Exception("Artifact 'artifact_id' and 'version' must be specified!")
|
|
71
71
|
logging.debug(f"Searching for '{artifact.artifact_id}' in {self.registry_url}...")
|
|
72
72
|
return self._search_func(artifact=artifact)
|
|
73
73
|
|
|
@@ -68,7 +68,7 @@ class MinioClient:
|
|
|
68
68
|
|
|
69
69
|
def put_file(self, bucket_name: str, path: str, local_path: str):
|
|
70
70
|
""""""
|
|
71
|
-
|
|
71
|
+
self.minio.fput_object(bucket_name, path, local_path)
|
|
72
72
|
|
|
73
73
|
def get_text_file_content(self, bucket_name: str, file_path: str):
|
|
74
74
|
""""""
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
3
|
+
|
|
2
4
|
import click
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
+
from rich import box
|
|
6
|
+
from rich.logging import RichHandler
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
|
|
5
9
|
from qubership_pipelines_common_library.v1.execution.exec_logger import ExecutionLogger
|
|
10
|
+
from qubership_pipelines_common_library.v1.utils.utils_logging import rich_console, ExtendedReprHighlighter, \
|
|
11
|
+
LevelColorFilter
|
|
6
12
|
|
|
7
13
|
DEFAULT_CONTEXT_FILE_PATH = 'context.yaml'
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
def utils_cli(func):
|
|
11
17
|
"""Decorator to add CLI options for logging level, context path and custom input params."""
|
|
18
|
+
|
|
12
19
|
@click.option('--log-level', default='INFO', show_default=True,
|
|
13
20
|
type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], case_sensitive=False),
|
|
14
21
|
help="Set the logging level")
|
|
@@ -20,23 +27,50 @@ def utils_cli(func):
|
|
|
20
27
|
@click.pass_context
|
|
21
28
|
def wrapper(ctx, *args, log_level, **kwargs):
|
|
22
29
|
ExecutionLogger.EXECUTION_LOG_LEVEL = getattr(logging, log_level.upper(), logging.INFO)
|
|
23
|
-
_configure_global_logger(logging.getLogger(), log_level
|
|
30
|
+
_configure_global_logger(logging.getLogger(), log_level)
|
|
31
|
+
_print_command_name()
|
|
24
32
|
_transform_kwargs(kwargs)
|
|
25
33
|
return ctx.invoke(func, *args, **kwargs)
|
|
34
|
+
|
|
26
35
|
return wrapper
|
|
27
36
|
|
|
28
37
|
|
|
29
|
-
def _configure_global_logger(global_logger: logging.Logger, log_level: str
|
|
38
|
+
def _configure_global_logger(global_logger: logging.Logger, log_level: str):
|
|
30
39
|
"""Configure the global logger with a specific log level and formatter."""
|
|
31
|
-
log_level_value = getattr(logging, log_level.upper(), logging.INFO)
|
|
32
40
|
global_logger.setLevel(logging.DEBUG)
|
|
33
41
|
if global_logger.hasHandlers():
|
|
34
42
|
global_logger.handlers.clear()
|
|
35
43
|
global_logger.propagate = True
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
rich_handler = RichHandler(
|
|
45
|
+
console=rich_console,
|
|
46
|
+
show_time=False,
|
|
47
|
+
show_level=False,
|
|
48
|
+
show_path=False,
|
|
49
|
+
enable_link_path=False,
|
|
50
|
+
rich_tracebacks=True,
|
|
51
|
+
tracebacks_show_locals=False,
|
|
52
|
+
markup=True,
|
|
53
|
+
highlighter=ExtendedReprHighlighter(),
|
|
54
|
+
)
|
|
55
|
+
rich_handler.addFilter(LevelColorFilter())
|
|
56
|
+
rich_handler.setFormatter(logging.Formatter(ExecutionLogger.LEVELNAME_COLORED_FORMAT))
|
|
57
|
+
log_level_value = getattr(logging, log_level.upper(), logging.INFO)
|
|
58
|
+
rich_handler.setLevel(log_level_value)
|
|
59
|
+
global_logger.addHandler(rich_handler)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _print_command_name():
|
|
63
|
+
try:
|
|
64
|
+
click_context = click.get_current_context()
|
|
65
|
+
command_name = click_context.command.name or click_context.info_name
|
|
66
|
+
except RuntimeError:
|
|
67
|
+
logging.getLogger().warning("Can't find command name.")
|
|
68
|
+
command_name = ""
|
|
69
|
+
|
|
70
|
+
command_panel = Panel(f"command_name = {command_name}", expand=False, padding=(0, 1), box=box.ROUNDED)
|
|
71
|
+
rich_console.print()
|
|
72
|
+
rich_console.print(command_panel)
|
|
73
|
+
rich_console.print()
|
|
40
74
|
|
|
41
75
|
|
|
42
76
|
def _transform_kwargs(kwargs):
|
|
@@ -66,3 +66,20 @@ class UtilsFile:
|
|
|
66
66
|
func(path)
|
|
67
67
|
else:
|
|
68
68
|
raise
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def create_parent_dirs(filepath):
|
|
72
|
+
if directory := os.path.dirname(filepath):
|
|
73
|
+
os.makedirs(directory, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def create_exec_dir(execution_folder_path: str | Path, exists_ok: bool = False) -> Path:
|
|
77
|
+
import shutil
|
|
78
|
+
exec_dir = Path(execution_folder_path)
|
|
79
|
+
if exec_dir.exists() and not exists_ok:
|
|
80
|
+
if exec_dir.is_dir():
|
|
81
|
+
shutil.rmtree(exec_dir)
|
|
82
|
+
else:
|
|
83
|
+
raise FileExistsError(f"Path '{execution_folder_path}' exists and is a file, not a directory.")
|
|
84
|
+
exec_dir.mkdir(parents=True, exist_ok=exists_ok)
|
|
85
|
+
return exec_dir
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.highlighter import ReprHighlighter
|
|
5
|
+
from rich.theme import Theme
|
|
6
|
+
|
|
7
|
+
soft_theme = Theme({
|
|
8
|
+
"repr.number": "rgb(180,200,255)",
|
|
9
|
+
"repr.bool_true": "rgb(140,230,140)",
|
|
10
|
+
"repr.bool_false": "rgb(230,140,140)",
|
|
11
|
+
"repr.none": "rgb(200,200,200) italic",
|
|
12
|
+
"repr.path": "rgb(190,220,160)",
|
|
13
|
+
"repr.filename": "rgb(160,210,190)",
|
|
14
|
+
"repr.url": "rgb(130,180,255) underline",
|
|
15
|
+
"repr.uuid": "rgb(200,180,220)",
|
|
16
|
+
"repr.attrib_name": "rgb(220,200,130)",
|
|
17
|
+
"repr.attrib_value": "rgb(170,190,220)",
|
|
18
|
+
"repr.str": "rgb(140,180,140)",
|
|
19
|
+
"repr.tag_name": "rgb(200,170,220)",
|
|
20
|
+
"repr.tag_value": "rgb(170,200,220)",
|
|
21
|
+
|
|
22
|
+
"repr.time": "rgb(160,190,220) italic",
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
rich_console = Console(
|
|
26
|
+
theme=soft_theme,
|
|
27
|
+
force_terminal=True,
|
|
28
|
+
no_color=False,
|
|
29
|
+
highlight=True,
|
|
30
|
+
width=150,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
level_colors = {
|
|
34
|
+
logging.DEBUG: "steel_blue",
|
|
35
|
+
logging.INFO: "light_sea_green",
|
|
36
|
+
logging.WARNING: "orange3",
|
|
37
|
+
logging.ERROR: "indian_red",
|
|
38
|
+
logging.CRITICAL: "bold medium_violet_red",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LevelColorFilter(logging.Filter):
|
|
43
|
+
def filter(self, record):
|
|
44
|
+
color = level_colors.get(record.levelno, "default")
|
|
45
|
+
record.levelname_color_open_tag = f"[{color}]"
|
|
46
|
+
record.levelname_color_close_tag = "[/]"
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ExtendedReprHighlighter(ReprHighlighter):
|
|
51
|
+
highlights = ReprHighlighter.highlights + [
|
|
52
|
+
r"(?P<time>\b([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:[.,]\d{1,9})?\b)",
|
|
53
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.artifact import Artifact
|
|
5
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.artifact_provider import ArtifactProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArtifactFinder:
|
|
9
|
+
"""
|
|
10
|
+
Allows searching for specific descriptor artifacts in different repositories without knowing full coordinates
|
|
11
|
+
(e.g. knowing only `artifact_id` and `version`, but not its `group_id`)
|
|
12
|
+
|
|
13
|
+
Supports different repository providers: Artifactory, Nexus, AWS, GCP, Azure
|
|
14
|
+
|
|
15
|
+
Provides different auth methods for Cloud Providers, implementing `CloudCredentialsProvider` interface
|
|
16
|
+
|
|
17
|
+
Start by initializing this client with one of implementations:
|
|
18
|
+
``finder = ArtifactFinder(artifact_provider=ArtifactoryProvider(registry_url="https://our_url", username="user", password="password"))``
|
|
19
|
+
|
|
20
|
+
Then find your artifacts using
|
|
21
|
+
``resource_urls = finder.find_artifact_urls(artifact_id='art_id', version='1.0.0', extension='json')``
|
|
22
|
+
|
|
23
|
+
Additionally, perform filtering of returned results (if you expect to find more than one artifact), and then download necessary artifacts with
|
|
24
|
+
``finder.download_artifact(one_of_the_returned_resource_urls, './my_artifact.json')``
|
|
25
|
+
|
|
26
|
+
For more complex providers (e.g. AWS Code Artifact), you need to use specific Credential Providers
|
|
27
|
+
As an example:
|
|
28
|
+
```
|
|
29
|
+
aws_creds = AwsCredentialsProvider().with_assume_role(...all the required params...).get_credentials()
|
|
30
|
+
aws_code_artifact_provider = AwsCodeArtifactProvider(creds=creds, domain='our_domain', project='our_project')
|
|
31
|
+
finder = ArtifactFinder(artifact_provider=aws_code_artifact_provider)
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, artifact_provider: ArtifactProvider, **kwargs):
|
|
36
|
+
if not artifact_provider:
|
|
37
|
+
raise Exception("Initialize ArtifactFinder with one of registry artifact providers first!")
|
|
38
|
+
self.provider = artifact_provider
|
|
39
|
+
|
|
40
|
+
def find_artifact_urls(self, artifact_id: str = None, version: str = None, group_id: str = None,
|
|
41
|
+
extension: str = "jar", artifact: Artifact = None) -> list[str]:
|
|
42
|
+
if not artifact:
|
|
43
|
+
artifact = Artifact(group_id=group_id, artifact_id=artifact_id, version=version, extension=extension)
|
|
44
|
+
if not artifact.artifact_id or not artifact.version:
|
|
45
|
+
raise Exception("Artifact 'artifact_id' and 'version' must be specified!")
|
|
46
|
+
logging.debug(f"Searching for '{artifact.artifact_id}:{artifact.version}' in {self.provider.get_provider_name()}...")
|
|
47
|
+
return self.provider.search_artifacts(artifact=artifact)
|
|
48
|
+
|
|
49
|
+
def download_artifact(self, resource_url: str, local_path: str | Path, artifact: Artifact = None):
|
|
50
|
+
from qubership_pipelines_common_library.v1.utils.utils_file import UtilsFile
|
|
51
|
+
download_path = Path(local_path)
|
|
52
|
+
if artifact:
|
|
53
|
+
download_path = download_path.joinpath(artifact.get_filename())
|
|
54
|
+
UtilsFile.create_parent_dirs(download_path)
|
|
55
|
+
logging.debug(f"Downloading artifact from '{resource_url}' to '{download_path}'...")
|
|
56
|
+
return self.provider.download_artifact(resource_url=resource_url, local_path=download_path)
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from botocore.config import Config
|
|
5
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials import Credentials
|
|
6
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials_provider import CloudCredentialsProvider
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AwsCredentialsProvider(CloudCredentialsProvider):
|
|
10
|
+
|
|
11
|
+
DEFAULT_ROLE_SESSION_NAME = "codeartifact-session"
|
|
12
|
+
|
|
13
|
+
access_key: str
|
|
14
|
+
secret_key: str
|
|
15
|
+
session_token: str
|
|
16
|
+
region_name: str
|
|
17
|
+
role_arn: str
|
|
18
|
+
role_session_name: str
|
|
19
|
+
_auth_type = None
|
|
20
|
+
|
|
21
|
+
class AuthType(StrEnum):
|
|
22
|
+
DIRECT = 'DIRECT'
|
|
23
|
+
ASSUME_ROLE = 'ASSUME_ROLE'
|
|
24
|
+
|
|
25
|
+
def with_direct_credentials(self,
|
|
26
|
+
access_key: str,
|
|
27
|
+
secret_key: str,
|
|
28
|
+
region_name: str,
|
|
29
|
+
session_token: str | None = None,
|
|
30
|
+
):
|
|
31
|
+
self.access_key = access_key
|
|
32
|
+
self.secret_key = secret_key
|
|
33
|
+
self.session_token = session_token
|
|
34
|
+
self.region_name = region_name
|
|
35
|
+
self.validate_mandatory_attrs(["access_key", "secret_key", "region_name"])
|
|
36
|
+
self._auth_type = self.AuthType.DIRECT
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def with_assume_role(self,
|
|
40
|
+
access_key: str,
|
|
41
|
+
secret_key: str,
|
|
42
|
+
region_name: str,
|
|
43
|
+
role_arn: str,
|
|
44
|
+
role_session_name: str | None = DEFAULT_ROLE_SESSION_NAME,
|
|
45
|
+
):
|
|
46
|
+
self.access_key = access_key
|
|
47
|
+
self.secret_key = secret_key
|
|
48
|
+
self.region_name = region_name
|
|
49
|
+
self.role_arn = role_arn
|
|
50
|
+
self.role_session_name = role_session_name
|
|
51
|
+
self.validate_mandatory_attrs(["access_key", "secret_key", "region_name", "role_arn", "role_session_name"])
|
|
52
|
+
self._auth_type = self.AuthType.ASSUME_ROLE
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def get_credentials(self) -> Credentials:
|
|
56
|
+
if self._auth_type is self.AuthType.DIRECT:
|
|
57
|
+
return self._get_direct_credentials()
|
|
58
|
+
elif self._auth_type is self.AuthType.ASSUME_ROLE:
|
|
59
|
+
return self._get_assume_role_credentials()
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("Need to initialize this provider with AuthType via .with_*auth_type* method first!")
|
|
62
|
+
|
|
63
|
+
def get_ecr_authorization_token(self) -> str:
|
|
64
|
+
creds = self.get_credentials()
|
|
65
|
+
ecr_client = boto3.client(
|
|
66
|
+
service_name="ecr",
|
|
67
|
+
config=Config(region_name=self.region_name),
|
|
68
|
+
aws_access_key_id=creds["access_key"],
|
|
69
|
+
aws_secret_access_key=creds["secret_key"],
|
|
70
|
+
aws_session_token=creds["session_token"],
|
|
71
|
+
)
|
|
72
|
+
ecr_authorization_token = ecr_client.get_authorization_token()
|
|
73
|
+
ecr_authorization_data = ecr_authorization_token["authorizationData"][0]
|
|
74
|
+
return ecr_authorization_data["authorizationToken"]
|
|
75
|
+
|
|
76
|
+
def _get_direct_credentials(self) -> Credentials:
|
|
77
|
+
return Credentials(
|
|
78
|
+
access_key=self.access_key,
|
|
79
|
+
secret_key=self.secret_key,
|
|
80
|
+
session_token=self.session_token,
|
|
81
|
+
region_name=self.region_name,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _get_assume_role_credentials(self) -> Credentials:
|
|
85
|
+
creds = self._assume_role()
|
|
86
|
+
return Credentials(
|
|
87
|
+
access_key=creds["AccessKeyId"],
|
|
88
|
+
secret_key=creds["SecretAccessKey"],
|
|
89
|
+
session_token=creds["SessionToken"],
|
|
90
|
+
role_arn=self.role_arn,
|
|
91
|
+
role_session_name=self.role_session_name,
|
|
92
|
+
region_name=self.region_name,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def _assume_role(self) -> dict:
|
|
96
|
+
sts_client = boto3.client(
|
|
97
|
+
service_name="sts",
|
|
98
|
+
config=Config(region_name=self.region_name),
|
|
99
|
+
aws_access_key_id=self.access_key,
|
|
100
|
+
aws_secret_access_key=self.secret_key,
|
|
101
|
+
)
|
|
102
|
+
assumed = sts_client.assume_role(
|
|
103
|
+
RoleArn=self.role_arn,
|
|
104
|
+
RoleSessionName=self.role_session_name,
|
|
105
|
+
)
|
|
106
|
+
return assumed["Credentials"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials import Credentials
|
|
5
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials_provider import CloudCredentialsProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AzureCredentialsProvider(CloudCredentialsProvider):
|
|
9
|
+
|
|
10
|
+
tenant_id: str
|
|
11
|
+
client_id: str
|
|
12
|
+
client_secret: str
|
|
13
|
+
target_resource: str
|
|
14
|
+
_auth_data: dict
|
|
15
|
+
_auth_type = None
|
|
16
|
+
|
|
17
|
+
class AuthType(StrEnum):
|
|
18
|
+
OAUTH2 = 'OAUTH2'
|
|
19
|
+
|
|
20
|
+
def with_oauth2(self,
|
|
21
|
+
tenant_id: str,
|
|
22
|
+
client_id: str,
|
|
23
|
+
client_secret: str,
|
|
24
|
+
target_resource: str,
|
|
25
|
+
):
|
|
26
|
+
self.tenant_id = tenant_id
|
|
27
|
+
self.client_id = client_id
|
|
28
|
+
self.client_secret = client_secret
|
|
29
|
+
self.target_resource = target_resource
|
|
30
|
+
self.validate_mandatory_attrs(["tenant_id", "client_id", "client_secret", "target_resource"])
|
|
31
|
+
self._auth_data = {
|
|
32
|
+
"grant_type": "client_credentials",
|
|
33
|
+
"client_id": client_id,
|
|
34
|
+
"client_secret": client_secret,
|
|
35
|
+
"scope": f"{target_resource}/.default"
|
|
36
|
+
}
|
|
37
|
+
self._auth_type = self.AuthType.OAUTH2
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def with_oauth2_custom_data(self,
|
|
41
|
+
tenant_id: str,
|
|
42
|
+
custom_auth_data: dict,
|
|
43
|
+
):
|
|
44
|
+
self.tenant_id = tenant_id
|
|
45
|
+
self._auth_data = custom_auth_data
|
|
46
|
+
self.validate_mandatory_attrs(["tenant_id", "_auth_data"])
|
|
47
|
+
self._auth_type = self.AuthType.OAUTH2
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def get_credentials(self) -> Credentials:
|
|
51
|
+
if self._auth_type is self.AuthType.OAUTH2:
|
|
52
|
+
return self._get_oauth2_credentials()
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError("Need to initialize this provider with AuthType via .with_*auth_type* method first!")
|
|
55
|
+
|
|
56
|
+
def _get_oauth2_credentials(self) -> Credentials:
|
|
57
|
+
token_url = f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token"
|
|
58
|
+
response = requests.post(
|
|
59
|
+
token_url,
|
|
60
|
+
data=self._auth_data,
|
|
61
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
62
|
+
)
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
response_json = response.json()
|
|
65
|
+
access_token = response_json.get("access_token")
|
|
66
|
+
if not access_token:
|
|
67
|
+
raise Exception(f"Failed to get access token from {token_url}: {response_json}")
|
|
68
|
+
|
|
69
|
+
return Credentials(
|
|
70
|
+
access_token=access_token,
|
|
71
|
+
tenant_id=self.tenant_id,
|
|
72
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from google.auth.transport.requests import AuthorizedSession, Request
|
|
6
|
+
|
|
7
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials import Credentials
|
|
8
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials_provider import CloudCredentialsProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GcpCredentialsProvider(CloudCredentialsProvider):
|
|
12
|
+
|
|
13
|
+
DEFAULT_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
|
|
14
|
+
DEFAULT_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt"
|
|
15
|
+
|
|
16
|
+
service_account_key_content: str
|
|
17
|
+
oidc_credential_source: dict
|
|
18
|
+
audience: str
|
|
19
|
+
scopes: list[str]
|
|
20
|
+
subject_token_type: str
|
|
21
|
+
_auth_type = None
|
|
22
|
+
|
|
23
|
+
class AuthType(StrEnum):
|
|
24
|
+
SA_KEY = 'SA_KEY'
|
|
25
|
+
OIDC_CREDS = 'OIDC_CREDS'
|
|
26
|
+
|
|
27
|
+
def with_service_account_key(self,
|
|
28
|
+
service_account_key_content: str | None = None,
|
|
29
|
+
service_account_key_path: str | Path | None = None,
|
|
30
|
+
scopes: list[str] = None,
|
|
31
|
+
):
|
|
32
|
+
if scopes is None:
|
|
33
|
+
scopes = self.DEFAULT_SCOPES
|
|
34
|
+
self.scopes = scopes
|
|
35
|
+
self.service_account_key_content = service_account_key_content
|
|
36
|
+
if service_account_key_path:
|
|
37
|
+
with open(service_account_key_path, 'r') as key_file:
|
|
38
|
+
self.service_account_key_content = key_file.read()
|
|
39
|
+
self.validate_mandatory_attrs(["service_account_key_content"])
|
|
40
|
+
self._auth_type = self.AuthType.SA_KEY
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def with_oidc_creds(self,
|
|
44
|
+
oidc_credential_source: dict,
|
|
45
|
+
audience: str,
|
|
46
|
+
oidc_token_file_path: str = None,
|
|
47
|
+
subject_token_type: str = DEFAULT_SUBJECT_TOKEN_TYPE,
|
|
48
|
+
scopes: list[str] = None,
|
|
49
|
+
):
|
|
50
|
+
if scopes is None:
|
|
51
|
+
scopes = self.DEFAULT_SCOPES
|
|
52
|
+
self.scopes = scopes
|
|
53
|
+
self.oidc_credential_source = oidc_credential_source
|
|
54
|
+
if oidc_token_file_path:
|
|
55
|
+
self.oidc_credential_source = {"file": oidc_token_file_path}
|
|
56
|
+
self.audience = audience
|
|
57
|
+
self.subject_token_type = subject_token_type
|
|
58
|
+
self.validate_mandatory_attrs(["oidc_credential_source", "audience"])
|
|
59
|
+
self._auth_type = self.AuthType.OIDC_CREDS
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def get_credentials(self) -> Credentials:
|
|
63
|
+
if self._auth_type == self.AuthType.SA_KEY:
|
|
64
|
+
from google.oauth2 import service_account
|
|
65
|
+
google_creds = service_account.Credentials.from_service_account_info(
|
|
66
|
+
info=json.loads(self.service_account_key_content),
|
|
67
|
+
scopes=self.scopes,
|
|
68
|
+
)
|
|
69
|
+
elif self._auth_type == self.AuthType.OIDC_CREDS:
|
|
70
|
+
from google.auth import identity_pool
|
|
71
|
+
google_creds = identity_pool.Credentials(
|
|
72
|
+
audience=self.audience,
|
|
73
|
+
subject_token_type=self.subject_token_type,
|
|
74
|
+
credential_source=self.oidc_credential_source,
|
|
75
|
+
scopes=self.scopes,
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError("Need to initialize this provider with AuthType via .with_*auth_type* method first!")
|
|
79
|
+
|
|
80
|
+
google_creds.refresh(Request())
|
|
81
|
+
creds = Credentials(
|
|
82
|
+
google_credentials_object=google_creds,
|
|
83
|
+
gcp_authorization_token=google_creds.token, # It can be used in Basic Authorization (in some Execution Commands)
|
|
84
|
+
authorized_session=AuthorizedSession(credentials=google_creds),
|
|
85
|
+
)
|
|
86
|
+
if self._auth_type == self.AuthType.SA_KEY:
|
|
87
|
+
creds.service_account_key_content = self.service_account_key_content
|
|
88
|
+
return creds
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class Artifact:
|
|
2
|
+
def __init__(self, group_id = None, artifact_id = None, version = None, extension='jar'):
|
|
3
|
+
self.group_id = group_id
|
|
4
|
+
self.artifact_id = artifact_id
|
|
5
|
+
self.version = version
|
|
6
|
+
self.extension = "jar" if not extension else extension
|
|
7
|
+
|
|
8
|
+
def is_snapshot(self):
|
|
9
|
+
return self.version and self.version.endswith("SNAPSHOT")
|
|
10
|
+
|
|
11
|
+
def get_filename(self) -> str:
|
|
12
|
+
return f"{self.artifact_id}-{self.version}.{self.extension}"
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def from_string(artifact_str: str):
|
|
16
|
+
parts = artifact_str.split(":")
|
|
17
|
+
if len(parts) == 3:
|
|
18
|
+
group, artifact, version = parts[0], parts[1], parts[-1]
|
|
19
|
+
return Artifact(group, artifact, version)
|
|
20
|
+
raise Exception("Invalid artifact string")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.artifact import Artifact
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArtifactProvider(ABC):
|
|
9
|
+
"""Base class for all artifact providers"""
|
|
10
|
+
|
|
11
|
+
TIMESTAMP_VERSION_PATTERN = "^(.*-)?([0-9]{8}\\.[0-9]{6}-[0-9]+)$"
|
|
12
|
+
|
|
13
|
+
def __init__(self, params: dict = None, **kwargs):
|
|
14
|
+
self.params = params if params else {}
|
|
15
|
+
self._session = requests.Session()
|
|
16
|
+
self._session.verify = self.params.get('verify', True)
|
|
17
|
+
self.timeout = self.params.get('timeout', None)
|
|
18
|
+
|
|
19
|
+
def generic_download(self, resource_url: str, local_path: str | Path):
|
|
20
|
+
response = self._session.get(url=resource_url, timeout=self.timeout)
|
|
21
|
+
response.raise_for_status()
|
|
22
|
+
with open(local_path, 'wb') as file:
|
|
23
|
+
file.write(response.content)
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def download_artifact(self, resource_url: str, local_path: str | Path, **kwargs) -> None:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def search_artifacts(self, artifact: Artifact, **kwargs) -> list[str]:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def get_provider_name(self) -> str:
|
|
35
|
+
pass
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Credentials(dict):
|
|
2
|
+
def __init__(self, *args, **kwargs):
|
|
3
|
+
super().__init__(*args, **kwargs)
|
|
4
|
+
self.__dict__ = self # allow attribute access
|
|
5
|
+
|
|
6
|
+
def __getitem__(self, key):
|
|
7
|
+
return self.get(key)
|
|
8
|
+
|
|
9
|
+
def __getattr__(self, key):
|
|
10
|
+
try:
|
|
11
|
+
return self[key]
|
|
12
|
+
except KeyError:
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
def __setattr__(self, key, value):
|
|
16
|
+
self[key] = value
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from qubership_pipelines_common_library.v2.artifacts_finder.model.credentials import Credentials
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CloudCredentialsProvider(ABC):
|
|
7
|
+
"""Base class for all cloud credentials"""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def get_credentials(self) -> Credentials:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def validate_mandatory_attrs(self, attrs_names) -> None:
|
|
14
|
+
missing_attrs = [attr for attr in attrs_names if getattr(self, attr, None) is None]
|
|
15
|
+
if missing_attrs:
|
|
16
|
+
raise ValueError(f"The following mandatory attributes are not set: {', '.join(missing_attrs)}")
|
|
File without changes
|