qubership-pipelines-common-library 2.0.1__tar.gz → 2.0.3__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-2.0.1 → qubership_pipelines_common_library-2.0.3}/PKG-INFO +2 -2
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/pyproject.toml +2 -3
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_cli.py +37 -26
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_logging.py +0 -1
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira/jira_add_ticket_comment_command.py +100 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira/jira_client.py +215 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira/jira_create_ticket_command.py +126 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira/jira_update_ticket_command.py +139 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira/jira_utils.py +19 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/notifications/__init__.py +0 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/notifications/send_email_command.py +150 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/notifications/send_webex_message_command.py +131 -0
- qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/podman/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/utils/crypto_utils.py +2 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/LICENSE +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/README.md +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/artifactory_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/execution/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/execution/exec_command.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/execution/exec_context.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/execution/exec_context_file.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/execution/exec_info.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/execution/exec_logger.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/git_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/github_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/gitlab_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/jenkins_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/kube_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/log_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/maven_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/minio_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/rest.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_aws.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_context.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_dictionary.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_file.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_json.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/utils/utils_string.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v1/webex_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/artifact_finder.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/auth/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/auth/aws_credentials.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/auth/azure_credentials.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/auth/gcp_credentials.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/model/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/model/artifact.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/model/artifact_provider.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/model/credentials.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/model/credentials_provider.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/providers/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/providers/artifactory.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/providers/aws_code_artifact.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/providers/azure_artifacts.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/providers/gcp_artifact_registry.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/artifacts_finder/providers/nexus.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/extensions/pipeline_data_importer.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/github/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/github/github_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/github/github_pipeline_data_importer.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/github/github_run_pipeline_command.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/github/safe_github_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/gitlab/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/gitlab/custom_extensions.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/gitlab/gitlab_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/gitlab/gitlab_pipeline_data_importer.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/gitlab/gitlab_run_pipeline_command.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/gitlab/safe_gitlab_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/jenkins/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/jenkins/custom_extensions.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/jenkins/jenkins_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/jenkins/jenkins_pipeline_data_importer.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/jenkins/jenkins_run_pipeline_command.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/jenkins/safe_jenkins_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1/qubership_pipelines_common_library/v2/podman → qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira}/__init__.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/podman/podman_command.md +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/podman/podman_command.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/sops/sops_client.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/utils/extension_utils.py +0 -0
- {qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/qubership_pipelines_common_library/v2/utils/retry_decorator.py +0 -0
{qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qubership-pipelines-common-library
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Qubership Pipelines common library
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -20,7 +20,7 @@ Requires-Dist: google-cloud-artifact-registry (>=1.16.1,<2.0.0)
|
|
|
20
20
|
Requires-Dist: http-exceptions (>=0.2.10,<0.3.0)
|
|
21
21
|
Requires-Dist: kubernetes (>=34.1.0,<35.0.0)
|
|
22
22
|
Requires-Dist: minio (>=7.2.12,<8.0.0)
|
|
23
|
-
Requires-Dist: python-gitlab (>=4.13.0,<
|
|
23
|
+
Requires-Dist: python-gitlab (>=4.13.0,<6.0.0)
|
|
24
24
|
Requires-Dist: python-jenkins (>=1.8.2,<2.0.0)
|
|
25
25
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
26
26
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
{qubership_pipelines_common_library-2.0.1 → qubership_pipelines_common_library-2.0.3}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "qubership-pipelines-common-library"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.3"
|
|
4
4
|
description = "Qubership Pipelines common library"
|
|
5
5
|
authors = ["Qubership"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -16,7 +16,7 @@ urllib3 = "^2.2.3"
|
|
|
16
16
|
python-jenkins = "^1.8.2"
|
|
17
17
|
GitPython = "^3.1.43"
|
|
18
18
|
http-exceptions = "^0.2.10"
|
|
19
|
-
python-gitlab = "
|
|
19
|
+
python-gitlab = ">=4.13.0,<6.0.0"
|
|
20
20
|
minio = "^7.2.12"
|
|
21
21
|
kubernetes = "^34.1.0"
|
|
22
22
|
webexpythonsdk = "2.0.1"
|
|
@@ -24,7 +24,6 @@ ghapi = "^1.0.6"
|
|
|
24
24
|
boto3 = "^1.39.4"
|
|
25
25
|
google-cloud-artifact-registry = "^1.16.1"
|
|
26
26
|
rich = "^14.2.0"
|
|
27
|
-
# jira = "3.10.5"
|
|
28
27
|
|
|
29
28
|
[tool.poetry.group.test.dependencies]
|
|
30
29
|
pytest = "^6.0.0"
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
|
-
|
|
4
|
+
import sys
|
|
4
5
|
import click
|
|
5
|
-
from rich import box
|
|
6
|
-
from rich.logging import RichHandler
|
|
7
|
-
from rich.panel import Panel
|
|
8
6
|
|
|
9
7
|
from qubership_pipelines_common_library.v1.execution.exec_logger import ExecutionLogger
|
|
10
|
-
from qubership_pipelines_common_library.v1.utils.
|
|
11
|
-
LevelColorFilter
|
|
8
|
+
from qubership_pipelines_common_library.v1.utils.utils_string import UtilsString
|
|
12
9
|
|
|
13
10
|
DEFAULT_CONTEXT_FILE_PATH = 'context.yaml'
|
|
14
11
|
|
|
@@ -37,26 +34,34 @@ def utils_cli(func):
|
|
|
37
34
|
|
|
38
35
|
def _configure_global_logger(global_logger: logging.Logger, log_level: str):
|
|
39
36
|
"""Configure the global logger with a specific log level and formatter."""
|
|
37
|
+
log_level_value = getattr(logging, log_level.upper(), logging.INFO)
|
|
40
38
|
global_logger.setLevel(logging.DEBUG)
|
|
41
39
|
if global_logger.hasHandlers():
|
|
42
40
|
global_logger.handlers.clear()
|
|
43
41
|
global_logger.propagate = True
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
42
|
+
if UtilsString.convert_to_bool(os.getenv('NO_RICH', False)):
|
|
43
|
+
stdout_handler = logging.StreamHandler(sys.stdout)
|
|
44
|
+
stdout_handler.setLevel(log_level_value)
|
|
45
|
+
stdout_handler.setFormatter(logging.Formatter(ExecutionLogger.DEFAULT_FORMAT))
|
|
46
|
+
global_logger.addHandler(stdout_handler)
|
|
47
|
+
else:
|
|
48
|
+
from rich.logging import RichHandler
|
|
49
|
+
from qubership_pipelines_common_library.v1.utils.utils_logging import rich_console, ExtendedReprHighlighter, LevelColorFilter
|
|
50
|
+
rich_handler = RichHandler(
|
|
51
|
+
console=rich_console,
|
|
52
|
+
show_time=False,
|
|
53
|
+
show_level=False,
|
|
54
|
+
show_path=False,
|
|
55
|
+
enable_link_path=False,
|
|
56
|
+
rich_tracebacks=True,
|
|
57
|
+
tracebacks_show_locals=False,
|
|
58
|
+
markup=True,
|
|
59
|
+
highlighter=ExtendedReprHighlighter(),
|
|
60
|
+
)
|
|
61
|
+
rich_handler.addFilter(LevelColorFilter())
|
|
62
|
+
rich_handler.setFormatter(logging.Formatter(ExecutionLogger.LEVELNAME_COLORED_FORMAT))
|
|
63
|
+
rich_handler.setLevel(log_level_value)
|
|
64
|
+
global_logger.addHandler(rich_handler)
|
|
60
65
|
|
|
61
66
|
|
|
62
67
|
def _print_command_name():
|
|
@@ -67,10 +72,16 @@ def _print_command_name():
|
|
|
67
72
|
logging.getLogger().warning("Can't find command name.")
|
|
68
73
|
command_name = ""
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
if UtilsString.convert_to_bool(os.getenv('NO_RICH', False)):
|
|
76
|
+
logging.info(f"command_name = {command_name}")
|
|
77
|
+
else:
|
|
78
|
+
from rich import box
|
|
79
|
+
from rich.panel import Panel
|
|
80
|
+
from qubership_pipelines_common_library.v1.utils.utils_logging import rich_console
|
|
81
|
+
command_panel = Panel(f"command_name = {command_name}", expand=False, padding=(0, 1), box=box.ROUNDED)
|
|
82
|
+
rich_console.print()
|
|
83
|
+
rich_console.print(command_panel)
|
|
84
|
+
rich_console.print()
|
|
74
85
|
|
|
75
86
|
|
|
76
87
|
def _transform_kwargs(kwargs):
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from qubership_pipelines_common_library.v1.execution.exec_command import ExecutionCommand
|
|
2
|
+
from qubership_pipelines_common_library.v2.jira.jira_client import JiraClient, AuthType
|
|
3
|
+
from qubership_pipelines_common_library.v2.jira.jira_utils import JiraUtils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JiraAddTicketComment(ExecutionCommand):
|
|
7
|
+
"""
|
|
8
|
+
Adds comment to JIRA ticket and retrieves latest comments.
|
|
9
|
+
|
|
10
|
+
Input Parameters Structure (this structure is expected inside "input_params.params" block):
|
|
11
|
+
```
|
|
12
|
+
{
|
|
13
|
+
"ticket": {
|
|
14
|
+
"id": "BUG-567", # REQUIRED: Ticket ID
|
|
15
|
+
"comment": "your comment body", # REQUIRED: Comment body
|
|
16
|
+
"latest_comments_count": 50, # OPTIONAL: Number of latest comments to fetch
|
|
17
|
+
},
|
|
18
|
+
"retry_timeout_seconds": 180, # OPTIONAL: Timeout for JIRA client operations in seconds (default: 180)
|
|
19
|
+
"retry_wait_seconds": 1, # OPTIONAL: Wait interval between retries in seconds (default: 1)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Systems Configuration (expected in "systems.jira" block):
|
|
24
|
+
```
|
|
25
|
+
{
|
|
26
|
+
"url": "https://your_cloud_jira.atlassian.net", # REQUIRED: JIRA server URL
|
|
27
|
+
"username": "your_username_or_email", # REQUIRED: JIRA user login or email
|
|
28
|
+
"password": "<your_token>", # REQUIRED: JIRA user token
|
|
29
|
+
"auth_type": "basic" # OPTIONAL: 'basic' or 'bearer'
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Command name: "jira-add-ticket-comment"
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
RETRY_TIMEOUT_SECONDS = 180 # default value, how many seconds to try
|
|
37
|
+
RETRY_WAIT_SECONDS = 1 # default value, how many seconds between tries
|
|
38
|
+
LATEST_COMMENTS_COUNT = 50 # default value, max amount of comments in response
|
|
39
|
+
|
|
40
|
+
def _validate(self):
|
|
41
|
+
names = [
|
|
42
|
+
"paths.input.params",
|
|
43
|
+
"paths.output.params",
|
|
44
|
+
"systems.jira.url",
|
|
45
|
+
"systems.jira.username",
|
|
46
|
+
"systems.jira.password",
|
|
47
|
+
"params.ticket.id",
|
|
48
|
+
"params.ticket.comment",
|
|
49
|
+
]
|
|
50
|
+
if not self.context.validate(names):
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
self.retry_timeout_seconds = int(self.context.input_param_get("params.retry_timeout_seconds", self.RETRY_TIMEOUT_SECONDS))
|
|
54
|
+
self.retry_wait_seconds = int(self.context.input_param_get("params.retry_wait_seconds", self.RETRY_WAIT_SECONDS))
|
|
55
|
+
|
|
56
|
+
self.jira_url = self.context.input_param_get("systems.jira.url").rstrip('/')
|
|
57
|
+
self.jira_username = self.context.input_param_get("systems.jira.username")
|
|
58
|
+
self.jira_password = self.context.input_param_get("systems.jira.password")
|
|
59
|
+
self.auth_type = self.context.input_param_get("systems.jira.auth_type", AuthType.BASIC)
|
|
60
|
+
|
|
61
|
+
self.ticket_key = self.context.input_param_get("params.ticket.id")
|
|
62
|
+
self.ticket_comment = self.context.input_param_get("params.ticket.comment")
|
|
63
|
+
self.latest_comments_count = int(self.context.input_param_get("params.ticket.latest_comments_count", self.LATEST_COMMENTS_COUNT))
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
def _execute(self):
|
|
67
|
+
self.context.logger.info("Running jira-add-ticket-comment")
|
|
68
|
+
self.jira_client = JiraClient.create_jira_client(
|
|
69
|
+
self.jira_url, self.jira_username, self.jira_password, self.auth_type,
|
|
70
|
+
self.retry_timeout_seconds, self.retry_wait_seconds,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if self.ticket_comment:
|
|
74
|
+
JiraUtils.add_ticket_comment(self)
|
|
75
|
+
|
|
76
|
+
total_comments = 0
|
|
77
|
+
parsed_latest_comments = []
|
|
78
|
+
try:
|
|
79
|
+
self.latest_comments = self.jira_client.get_latest_ticket_comments(self.ticket_key, max_results=self.latest_comments_count)
|
|
80
|
+
total_comments = len(self.latest_comments)
|
|
81
|
+
parsed_latest_comments = [self._parse_comment(comment) for comment in self.latest_comments]
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.context.logger.warning(f"Can't get latest ticket comments. Response exception: {str(e)}")
|
|
84
|
+
|
|
85
|
+
self.context.output_param_set("params.ticket.id", self.ticket_key)
|
|
86
|
+
self.context.output_param_set("params.ticket.url", f"{self.jira_url}/browse/{self.ticket_key}")
|
|
87
|
+
self.context.output_param_set("params.ticket.total_comments", total_comments)
|
|
88
|
+
self.context.output_param_set("params.ticket.latest_comments", parsed_latest_comments)
|
|
89
|
+
self.context.output_params_save()
|
|
90
|
+
self.context.logger.info("Add ticket comment request executed. See output params for details")
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _parse_comment(comment):
|
|
94
|
+
return {
|
|
95
|
+
"body": comment.get("body"),
|
|
96
|
+
"created": comment.get("created"),
|
|
97
|
+
"updated": comment.get("updated"),
|
|
98
|
+
"author": JiraClient.serialize_person_ref(comment.get("author", {})),
|
|
99
|
+
"updateAuthor": JiraClient.serialize_person_ref(comment.get("updateAuthor", {})),
|
|
100
|
+
}
|
qubership_pipelines_common_library-2.0.3/qubership_pipelines_common_library/v2/jira/jira_client.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from typing import Any
|
|
8
|
+
from requests import Response
|
|
9
|
+
from requests.auth import HTTPBasicAuth
|
|
10
|
+
from qubership_pipelines_common_library.v2.utils.retry_decorator import RetryDecorator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuthType(StrEnum):
|
|
14
|
+
BASIC = 'basic'
|
|
15
|
+
BEARER = 'bearer'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JiraClient:
|
|
19
|
+
|
|
20
|
+
DEFAULT_FIELD_NAMES_FILTER = [
|
|
21
|
+
"fixVersions",
|
|
22
|
+
"resolution",
|
|
23
|
+
"priority",
|
|
24
|
+
"labels",
|
|
25
|
+
"versions",
|
|
26
|
+
"assignee",
|
|
27
|
+
"status",
|
|
28
|
+
"components",
|
|
29
|
+
"creator",
|
|
30
|
+
"reporter",
|
|
31
|
+
"issuetype",
|
|
32
|
+
"project",
|
|
33
|
+
"resolutiondate",
|
|
34
|
+
"created",
|
|
35
|
+
"updated",
|
|
36
|
+
"description",
|
|
37
|
+
"summary",
|
|
38
|
+
"customfield_10014", # Found in
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
API_VERSION = "2"
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
@RetryDecorator(condition_func=lambda client: client is not None)
|
|
45
|
+
def create_jira_client(cls, host: str, user: str, password: str, auth_type: str,
|
|
46
|
+
retry_timeout_seconds: int = 180, retry_wait_seconds: int = 1):
|
|
47
|
+
return cls(host, user, password, auth_type)
|
|
48
|
+
|
|
49
|
+
def __init__(self, host: str, user: str, password: str, auth_type: str = AuthType.BASIC):
|
|
50
|
+
self.host = host.rstrip("/")
|
|
51
|
+
self.user = user
|
|
52
|
+
self.password = password
|
|
53
|
+
self.session = requests.Session()
|
|
54
|
+
self.session.verify = os.getenv("PYTHONHTTPSVERIFY", "1") != "0"
|
|
55
|
+
if auth_type.lower() == AuthType.BEARER:
|
|
56
|
+
self.session.headers.update({"Authorization": f"Bearer {password}"})
|
|
57
|
+
else:
|
|
58
|
+
self.session.auth = HTTPBasicAuth(user, password)
|
|
59
|
+
self.headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
|
60
|
+
self.session.headers.update(self.headers)
|
|
61
|
+
self.logger = logging.getLogger()
|
|
62
|
+
try:
|
|
63
|
+
self.server_info = self.get_server_info()
|
|
64
|
+
self.deployment_type = self.server_info.get("deploymentType", "Server")
|
|
65
|
+
except Exception as e:
|
|
66
|
+
self.logger.info(f"Could not get Jira instance version, assuming 'Server': {e}")
|
|
67
|
+
self.deployment_type = "Server"
|
|
68
|
+
self.logger.info(f"Jira Client configured for {self.host}, deployment type: {self.deployment_type}")
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def _is_cloud(self) -> bool:
|
|
72
|
+
return self.deployment_type == "Cloud"
|
|
73
|
+
|
|
74
|
+
def get_server_info(self) -> dict[str, Any]:
|
|
75
|
+
retry = 0
|
|
76
|
+
j = self._get_json("serverInfo")
|
|
77
|
+
while not j and retry < 3:
|
|
78
|
+
retry += 1
|
|
79
|
+
j = self._get_json("serverInfo")
|
|
80
|
+
return j
|
|
81
|
+
|
|
82
|
+
@RetryDecorator(condition_func=lambda response: response is not None and (response.ok or response.status_code == 400)) # do not retry BadRequest
|
|
83
|
+
def add_ticket_comment(self, ticket_id: str, comment: str, retry_timeout_seconds: int = 180, retry_wait_seconds: int = 1) -> Response:
|
|
84
|
+
response = self.session.post(
|
|
85
|
+
url=f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}/comment",
|
|
86
|
+
data=json.dumps({"body": comment})
|
|
87
|
+
)
|
|
88
|
+
self.logger.debug(f"Add ticket (id={ticket_id}) comment response: status_code = {response.status_code}, body = {response.text}")
|
|
89
|
+
return response
|
|
90
|
+
|
|
91
|
+
def get_latest_ticket_comments(self, ticket_id: str, max_results: int = 50) -> list:
|
|
92
|
+
response = self.session.get(f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}/comment?maxResults={max_results}&orderBy=-created")
|
|
93
|
+
self.logger.debug(f"Get ticket (id={ticket_id}) comments response: status_code = {response.status_code}, body = {response.text}")
|
|
94
|
+
response.raise_for_status()
|
|
95
|
+
return response.json().get("comments", [])
|
|
96
|
+
|
|
97
|
+
@RetryDecorator(condition_func=lambda response: response is not None and (response.ok or response.status_code == 400))
|
|
98
|
+
def create_ticket(self, ticket_fields: dict, retry_timeout_seconds: int = 180, retry_wait_seconds: int = 1) -> Response:
|
|
99
|
+
body = {"fields": ticket_fields}
|
|
100
|
+
response = self.session.post(f"{self.host}/rest/api/{self.API_VERSION}/issue", data=json.dumps(body))
|
|
101
|
+
self.logger.debug(f"Create ticket response: status_code = {response.status_code}, body = {response.text}")
|
|
102
|
+
return response
|
|
103
|
+
|
|
104
|
+
def get_createmeta_fields(self, project_key: str, issue_type_name: str) -> dict:
|
|
105
|
+
response = self.session.get(f"{self.host}/rest/api/{self.API_VERSION}/issue/createmeta/{project_key}/issuetypes?maxResults=100")
|
|
106
|
+
self.logger.debug(f"Get createmeta for project (id={project_key}) response: status_code = {response.status_code}, body = {response.text}")
|
|
107
|
+
if not response.ok:
|
|
108
|
+
self.logger.warning(f"Can't get issuetypes by projectKey = {project_key}. Response status = {response.status_code}")
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
issue_types = response.json().get("issueTypes" if self._is_cloud else "values", [])
|
|
112
|
+
issue_type_ids = [issue_type.get("id") for issue_type in issue_types if issue_type.get("name") == issue_type_name]
|
|
113
|
+
if not issue_type_ids:
|
|
114
|
+
self.logger.warning(f"Can't find issue type id by issue_type_name = {issue_type_name}")
|
|
115
|
+
return {}
|
|
116
|
+
|
|
117
|
+
response = self.session.get(f"{self.host}/rest/api/{self.API_VERSION}/issue/createmeta/{project_key}/issuetypes/{issue_type_ids[0]}?maxResults=100")
|
|
118
|
+
self.logger.debug(f"Get createmeta for issuetype (id={issue_type_ids[0]}) response: status_code = {response.status_code}, body = {response.text}")
|
|
119
|
+
if not response.ok:
|
|
120
|
+
self.logger.warning(f"Can't get createmeta by projectKey = {project_key} and issue_type_id = {issue_type_ids[0]}. Response status = {response.status_code}")
|
|
121
|
+
return {}
|
|
122
|
+
|
|
123
|
+
fields = response.json().get("fields" if self._is_cloud else "values", [])
|
|
124
|
+
return {field["fieldId"]: field for field in fields}
|
|
125
|
+
|
|
126
|
+
def get_ticket_fields(self, ticket_id: str, field_names_filter: list) -> dict:
|
|
127
|
+
response = self.session.get(f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}?fields={','.join(field_names_filter)}")
|
|
128
|
+
self.logger.debug(f"Get ticket fields (id={ticket_id}) response: status_code = {response.status_code}, body = {response.text}")
|
|
129
|
+
if not response.ok:
|
|
130
|
+
self.logger.warning(f"Can't get ticket info by ticket_id = {ticket_id}. Response status = {response.status_code}")
|
|
131
|
+
return {}
|
|
132
|
+
return self._transform_ticket_fields(response.json().get("fields", {}))
|
|
133
|
+
|
|
134
|
+
def get_editmeta_fields(self, ticket_id: str) -> dict:
|
|
135
|
+
response = self.session.get(f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}/editmeta")
|
|
136
|
+
self.logger.debug(f"Get editmeta for ticket (id={ticket_id}) response: status_code = {response.status_code}, body = {response.text}")
|
|
137
|
+
if not response.ok:
|
|
138
|
+
self.logger.warning(f"Can't get ticket {ticket_id} editmeta. Response status = {response.status_code}")
|
|
139
|
+
return {}
|
|
140
|
+
return response.json().get("fields", {})
|
|
141
|
+
|
|
142
|
+
@RetryDecorator(condition_func=lambda response: response is not None and (response.ok or response.status_code == 400))
|
|
143
|
+
def update_ticket(self, ticket_id: str, ticket_fields: dict,
|
|
144
|
+
retry_timeout_seconds: int = 180, retry_wait_seconds: int = 1) -> Response:
|
|
145
|
+
body = {"fields": ticket_fields}
|
|
146
|
+
response = self.session.put(f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}", data=json.dumps(body))
|
|
147
|
+
self.logger.debug(f"Update ticket (id={ticket_id}) response: status_code = {response.status_code}, body = {response.text}")
|
|
148
|
+
return response
|
|
149
|
+
|
|
150
|
+
def get_ticket_transitions(self, ticket_id: str):
|
|
151
|
+
response = self.session.get(f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}/transitions?expand=transitions.fields")
|
|
152
|
+
self.logger.debug(f"Get ticket (id={ticket_id}) transitions response: status_code = {response.status_code}, body = {response.text}")
|
|
153
|
+
if not response.ok:
|
|
154
|
+
self.logger.warning(f"Ticket {ticket_id} transitions are not found in response. Response status = {response.status_code}")
|
|
155
|
+
return []
|
|
156
|
+
return response.json().get("transitions", [])
|
|
157
|
+
|
|
158
|
+
def find_applicable_transition(self, transitions: list, status_name: str, transition_name: str = ""):
|
|
159
|
+
transitions_by_next_status = [transition for transition in transitions if status_name.lower() == transition.get("to", {}).get("name", "").lower()]
|
|
160
|
+
if not transitions_by_next_status:
|
|
161
|
+
self.logger.error(f"Status '{status_name}' is not found in transitions.")
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
if len(transitions_by_next_status) > 1:
|
|
165
|
+
self.logger.warning(f"Found more than one transition to status '{status_name}'")
|
|
166
|
+
if transition_name:
|
|
167
|
+
transitions_by_name = [transition for transition in transitions_by_next_status if transition_name.lower() == transition.get("name", "").lower()]
|
|
168
|
+
if transitions_by_name:
|
|
169
|
+
return transitions_by_name[0]
|
|
170
|
+
else:
|
|
171
|
+
self.logger.warning(f"Transition '{transition_name}' is not found in transitions. Will use first found transition by status '{status_name}'.")
|
|
172
|
+
|
|
173
|
+
return transitions_by_next_status[0]
|
|
174
|
+
|
|
175
|
+
def perform_ticket_transition(self, ticket_id: str, transition_id: str, transition_fields):
|
|
176
|
+
body = {"transition": {"id": transition_id}}
|
|
177
|
+
if transition_fields:
|
|
178
|
+
body["fields"] = transition_fields
|
|
179
|
+
response = self.session.post(f"{self.host}/rest/api/{self.API_VERSION}/issue/{ticket_id}/transitions", data=json.dumps(body))
|
|
180
|
+
self.logger.debug(f"Perform transition for ticket (id={ticket_id}) response: status_code = {response.status_code}, body = {response.text}")
|
|
181
|
+
return response
|
|
182
|
+
|
|
183
|
+
def _get_json(self, path: str, params: dict[str, Any] | None = None, use_post: bool = False) -> dict | list:
|
|
184
|
+
url = f"{self.host}/rest/api/{self.API_VERSION}/{path}"
|
|
185
|
+
response = (
|
|
186
|
+
self.session.post(url, data=json.dumps(params))
|
|
187
|
+
if use_post
|
|
188
|
+
else self.session.get(url, params=params)
|
|
189
|
+
)
|
|
190
|
+
return response.json()
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def filter_ticket_fields(ticket_fields: dict, meta_fields_filter: dict) -> dict:
|
|
194
|
+
return {field_key: field_value for field_key, field_value in ticket_fields.items() if field_key in meta_fields_filter.keys()}
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _transform_ticket_fields(ticket_fields_json: dict):
|
|
198
|
+
filtered_fields = {}
|
|
199
|
+
for k, v in ticket_fields_json.items():
|
|
200
|
+
if isinstance(v, dict) and "emailAddress" in v:
|
|
201
|
+
filtered_fields[k] = JiraClient.serialize_person_ref(v)
|
|
202
|
+
else:
|
|
203
|
+
filtered_fields[k] = v
|
|
204
|
+
return filtered_fields
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def serialize_person_ref(person: dict) -> dict | None:
|
|
208
|
+
if person:
|
|
209
|
+
return {
|
|
210
|
+
"displayName": person.get("displayName", None),
|
|
211
|
+
"emailAddress": person.get("emailAddress", None),
|
|
212
|
+
"name": person.get("name", None),
|
|
213
|
+
"key": person.get("key", None),
|
|
214
|
+
}
|
|
215
|
+
return None
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from qubership_pipelines_common_library.v1.execution.exec_command import ExecutionCommand
|
|
4
|
+
from qubership_pipelines_common_library.v2.jira.jira_client import JiraClient, AuthType
|
|
5
|
+
from qubership_pipelines_common_library.v2.jira.jira_utils import JiraUtils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JiraCreateTicket(ExecutionCommand):
|
|
9
|
+
"""
|
|
10
|
+
Creates new issue/ticket in JIRA project.
|
|
11
|
+
|
|
12
|
+
Input Parameters Structure (this structure is expected inside "input_params.params" block):
|
|
13
|
+
```
|
|
14
|
+
{
|
|
15
|
+
"ticket": {
|
|
16
|
+
"fields: { # REQUIRED: Dict structure that will be used as ticket-creation-body, without transformations
|
|
17
|
+
"project": {"key": "<YOUR_PROJECT_KEY>"}, # REQUIRED: Project Key
|
|
18
|
+
"issuetype": {"name": "Bug"}, # REQUIRED: Issue type name
|
|
19
|
+
"priority": {"name": "High"}, # OPTIONAL: Other ticket fields with different formats, depending on your Project configuration
|
|
20
|
+
"duedate": "2030-02-20", # OPTIONAL: Text-value fields need no dict wrappers
|
|
21
|
+
"summary": "[SOME_LABEL] Ticket Subject",
|
|
22
|
+
"description": "Ticket body",
|
|
23
|
+
"components": [{"name":"COMPONENT NAME"}],
|
|
24
|
+
"labels": ["Test_Label1"],
|
|
25
|
+
},
|
|
26
|
+
"comment": "your comment body", # OPTIONAL: Comment to add to created ticket
|
|
27
|
+
"field_names_filter": "summary,issuetype,creator,status", # OPTIONAL: Comma-separated names of fields to extract from created ticket to output params
|
|
28
|
+
},
|
|
29
|
+
"retry_timeout_seconds": 180, # OPTIONAL: Timeout for JIRA client operations in seconds (default: 180)
|
|
30
|
+
"retry_wait_seconds": 1, # OPTIONAL: Wait interval between retries in seconds (default: 1)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Systems Configuration (expected in "systems.jira" block):
|
|
35
|
+
```
|
|
36
|
+
{
|
|
37
|
+
"url": "https://your_cloud_jira.atlassian.net", # REQUIRED: JIRA server URL
|
|
38
|
+
"username": "your_username_or_email", # REQUIRED: JIRA user login or email
|
|
39
|
+
"password": "<your_token>", # REQUIRED: JIRA user token
|
|
40
|
+
"auth_type": "basic" # OPTIONAL: 'basic' or 'bearer'
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Command name: "jira-create-ticket"
|
|
45
|
+
"""
|
|
46
|
+
RETRY_TIMEOUT_SECONDS = 180 # default value, how many seconds to try
|
|
47
|
+
RETRY_WAIT_SECONDS = 1 # default value, how many seconds between tries
|
|
48
|
+
|
|
49
|
+
def _validate(self):
|
|
50
|
+
names = [
|
|
51
|
+
"paths.input.params",
|
|
52
|
+
"paths.output.params",
|
|
53
|
+
"systems.jira.url",
|
|
54
|
+
"systems.jira.username",
|
|
55
|
+
"systems.jira.password",
|
|
56
|
+
"params.ticket.fields",
|
|
57
|
+
]
|
|
58
|
+
if not self.context.validate(names):
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
self.retry_timeout_seconds = int(self.context.input_param_get("params.retry_timeout_seconds", self.RETRY_TIMEOUT_SECONDS))
|
|
62
|
+
self.retry_wait_seconds = int(self.context.input_param_get("params.retry_wait_seconds", self.RETRY_WAIT_SECONDS))
|
|
63
|
+
|
|
64
|
+
self.jira_url = self.context.input_param_get("systems.jira.url").rstrip('/')
|
|
65
|
+
self.jira_username = self.context.input_param_get("systems.jira.username")
|
|
66
|
+
self.jira_password = self.context.input_param_get("systems.jira.password")
|
|
67
|
+
self.auth_type = self.context.input_param_get("systems.jira.auth_type", AuthType.BASIC)
|
|
68
|
+
|
|
69
|
+
self.ticket_comment = self.context.input_param_get("params.ticket.comment")
|
|
70
|
+
self.ticket_fields = self.context.input_param_get("params.ticket.fields", {})
|
|
71
|
+
self.project_key = self.ticket_fields.get('project', {}).get('key')
|
|
72
|
+
self.issue_type_name = self.ticket_fields.get('issuetype', {}).get('name')
|
|
73
|
+
|
|
74
|
+
if not self.project_key or not self.issue_type_name:
|
|
75
|
+
self.context.logger.error("Can't find project.key and/or issuetype.name in input parameters")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
if not self._validate_mandatory_ticket_fields(self.ticket_fields):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
if field_names_filter := self.context.input_param_get("params.ticket.field_names_filter"):
|
|
82
|
+
self.field_names_filter = [x.strip() for x in re.split(r'[,;]+', field_names_filter)]
|
|
83
|
+
else:
|
|
84
|
+
self.field_names_filter = JiraClient.DEFAULT_FIELD_NAMES_FILTER
|
|
85
|
+
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
def _validate_mandatory_ticket_fields(self, ticket_fields):
|
|
89
|
+
valid = True
|
|
90
|
+
for field_key in ["project", "issuetype", "summary"]:
|
|
91
|
+
if field_key not in ticket_fields:
|
|
92
|
+
valid = False
|
|
93
|
+
self.context.logger.error(f"Parameter '{field_key}' is mandatory but not found in ticket params map")
|
|
94
|
+
return valid
|
|
95
|
+
|
|
96
|
+
def _execute(self):
|
|
97
|
+
self.context.logger.info("Running jira-create-ticket")
|
|
98
|
+
self.context.logger.info(f"Creating ticket in project {self.project_key}, type {self.issue_type_name}")
|
|
99
|
+
self.jira_client = JiraClient.create_jira_client(
|
|
100
|
+
self.jira_url, self.jira_username, self.jira_password, self.auth_type,
|
|
101
|
+
self.retry_timeout_seconds, self.retry_wait_seconds,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
createmeta_fields = self.jira_client.get_createmeta_fields(self.project_key, self.issue_type_name)
|
|
105
|
+
self.filtered_ticket_fields = JiraClient.filter_ticket_fields(self.ticket_fields, createmeta_fields)
|
|
106
|
+
self.context.logger.debug(f"Filtered ticket fields: {self.filtered_ticket_fields}")
|
|
107
|
+
|
|
108
|
+
create_ticket_response = self.jira_client.create_ticket(self.filtered_ticket_fields,
|
|
109
|
+
retry_timeout_seconds=self.retry_timeout_seconds,
|
|
110
|
+
retry_wait_seconds=self.retry_wait_seconds)
|
|
111
|
+
if not create_ticket_response.ok:
|
|
112
|
+
self._exit(False, f"Can't create ticket. Response status: {create_ticket_response.status_code}")
|
|
113
|
+
self.ticket_key = create_ticket_response.json().get('key')
|
|
114
|
+
self.context.logger.info(f"Ticket created successfully: {self.ticket_key}")
|
|
115
|
+
|
|
116
|
+
if self.ticket_comment:
|
|
117
|
+
JiraUtils.add_ticket_comment(self)
|
|
118
|
+
|
|
119
|
+
self.context.output_param_set("params.ticket.id", self.ticket_key)
|
|
120
|
+
self.context.output_param_set("params.ticket.url", f"{self.jira_url}/browse/{self.ticket_key}")
|
|
121
|
+
|
|
122
|
+
filtered_response_fields = self.jira_client.get_ticket_fields(self.ticket_key, self.field_names_filter)
|
|
123
|
+
self.context.output_param_set("params.ticket.fields", filtered_response_fields)
|
|
124
|
+
|
|
125
|
+
self.context.output_params_save()
|
|
126
|
+
self.context.logger.info("JIRA ticket creation completed successfully")
|