regscale-cli 6.25.1.0__py3-none-any.whl → 6.27.0.0__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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +19 -4
- regscale/core/app/internal/evidence.py +419 -2
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +142 -1
- regscale/integrations/commercial/tenablev2/scanner.py +0 -1
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +64 -79
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +161 -165
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
- regscale/integrations/commercial/wizv2/issue.py +1 -1
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +1 -1
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +39 -99
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +60 -41
- regscale/integrations/control_matcher.py +377 -0
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +277 -153
- regscale/models/integration_models/cisa_kev_data.json +282 -9
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
- regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +47 -22
- regscale/models/regscale_models/issue.py +256 -95
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +1397 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -8,11 +8,10 @@ import tempfile
|
|
|
8
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
9
|
from datetime import datetime, timedelta
|
|
10
10
|
from io import BytesIO
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union, Literal
|
|
12
13
|
from urllib.parse import urljoin
|
|
13
14
|
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
15
|
if TYPE_CHECKING:
|
|
17
16
|
from regscale.core.app.application import Application
|
|
18
17
|
|
|
@@ -40,7 +39,7 @@ from regscale.models import regscale_id, regscale_module
|
|
|
40
39
|
from regscale.models.regscale_models.file import File
|
|
41
40
|
from regscale.models.regscale_models.issue import Issue
|
|
42
41
|
from regscale.models.regscale_models.task import Task
|
|
43
|
-
from regscale.
|
|
42
|
+
from regscale.integrations.variables import ScannerVariables
|
|
44
43
|
|
|
45
44
|
job_progress = create_progress_object()
|
|
46
45
|
logger = create_logger()
|
|
@@ -104,6 +103,12 @@ def jira():
|
|
|
104
103
|
help="Custom JQL query for filtering Jira issues.",
|
|
105
104
|
required=False,
|
|
106
105
|
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--poams",
|
|
108
|
+
"-p",
|
|
109
|
+
is_flag=True,
|
|
110
|
+
help="Whether to create/update the incoming issues from Jira as POAMs in RegScale.",
|
|
111
|
+
)
|
|
107
112
|
def issues(
|
|
108
113
|
regscale_id: int,
|
|
109
114
|
regscale_module: str,
|
|
@@ -112,6 +117,7 @@ def issues(
|
|
|
112
117
|
sync_attachments: bool = True,
|
|
113
118
|
token_auth: bool = False,
|
|
114
119
|
jql: Optional[str] = None,
|
|
120
|
+
poams: bool = False,
|
|
115
121
|
):
|
|
116
122
|
"""Sync issues from Jira into RegScale."""
|
|
117
123
|
sync_regscale_and_jira(
|
|
@@ -122,6 +128,7 @@ def issues(
|
|
|
122
128
|
sync_attachments=sync_attachments,
|
|
123
129
|
token_auth=token_auth,
|
|
124
130
|
jql=jql,
|
|
131
|
+
use_poams=poams,
|
|
125
132
|
)
|
|
126
133
|
|
|
127
134
|
|
|
@@ -219,6 +226,7 @@ def sync_regscale_and_jira(
|
|
|
219
226
|
sync_tasks_only: bool = False,
|
|
220
227
|
token_auth: bool = False,
|
|
221
228
|
jql: Optional[str] = None,
|
|
229
|
+
use_poams: Optional[bool] = False,
|
|
222
230
|
) -> None:
|
|
223
231
|
"""
|
|
224
232
|
Sync issues, bidirectionally, from Jira into RegScale as issues
|
|
@@ -231,12 +239,19 @@ def sync_regscale_and_jira(
|
|
|
231
239
|
:param bool sync_tasks_only: Whether to sync only tasks from Jira, defaults to False
|
|
232
240
|
:param bool token_auth: Use token authentication for Jira API, defaults to False
|
|
233
241
|
:param Optional[str] jql: Custom JQL query for filtering Jira issues/tasks, defaults to None
|
|
242
|
+
:param Optional[bool] use_poams: Whether to mark the incoming issues as POAMs in RegScale, defaults to False
|
|
234
243
|
:rtype: None
|
|
235
244
|
"""
|
|
236
245
|
app = check_license()
|
|
237
246
|
api = Api()
|
|
238
247
|
config = app.config
|
|
239
248
|
|
|
249
|
+
# Load custom fields configuration from init.yaml
|
|
250
|
+
if custom_fields := config.get("jiraCustomFields", {}):
|
|
251
|
+
logger.info("Custom field mappings loaded from config: %s", custom_fields)
|
|
252
|
+
else:
|
|
253
|
+
logger.debug("No custom field mappings found in configuration")
|
|
254
|
+
|
|
240
255
|
# see if provided RegScale Module is an accepted option
|
|
241
256
|
verify_provided_module(parent_module)
|
|
242
257
|
|
|
@@ -296,35 +311,20 @@ def sync_regscale_and_jira(
|
|
|
296
311
|
api=api,
|
|
297
312
|
sync_attachments=sync_attachments,
|
|
298
313
|
attachments=regscale_attachments,
|
|
314
|
+
custom_fields=custom_fields,
|
|
299
315
|
):
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
)
|
|
306
|
-
# create threads to analyze Jira issues and RegScale issues
|
|
307
|
-
create_threads(
|
|
308
|
-
process=update_regscale_issues,
|
|
309
|
-
args=(
|
|
310
|
-
regscale_objects_to_update,
|
|
311
|
-
updating_issues,
|
|
312
|
-
),
|
|
313
|
-
thread_count=len(regscale_objects_to_update),
|
|
314
|
-
)
|
|
315
|
-
# output the final result
|
|
316
|
-
logger.info(
|
|
317
|
-
"%i/%i %s(s) updated in RegScale.",
|
|
318
|
-
len(regscale_objects_to_update),
|
|
319
|
-
len(update_counter),
|
|
320
|
-
output_str,
|
|
321
|
-
)
|
|
316
|
+
for regscale_object in regscale_objects_to_update:
|
|
317
|
+
regscale_object.save(bulk=True)
|
|
318
|
+
if isinstance(regscale_objects[0], Issue):
|
|
319
|
+
Issue.bulk_save()
|
|
320
|
+
elif isinstance(regscale_objects[0], Task):
|
|
321
|
+
Task.bulk_save()
|
|
322
322
|
else:
|
|
323
323
|
logger.info("No %s(s) need to be updated in RegScale.", output_str)
|
|
324
324
|
|
|
325
325
|
if jira_objects:
|
|
326
326
|
return sync_regscale_objects_to_jira(
|
|
327
|
-
jira_objects, regscale_objects, sync_attachments, app, parent_id, parent_module, sync_tasks_only
|
|
327
|
+
jira_objects, regscale_objects, sync_attachments, app, parent_id, parent_module, sync_tasks_only, use_poams
|
|
328
328
|
)
|
|
329
329
|
logger.info("No %s need to be analyzed from Jira.", output_str)
|
|
330
330
|
|
|
@@ -337,6 +337,7 @@ def sync_regscale_objects_to_jira(
|
|
|
337
337
|
parent_id: int,
|
|
338
338
|
parent_module: str,
|
|
339
339
|
sync_tasks_only: bool,
|
|
340
|
+
use_poams: Optional[bool] = False,
|
|
340
341
|
):
|
|
341
342
|
"""
|
|
342
343
|
Sync issues from Jira to RegScale
|
|
@@ -348,6 +349,7 @@ def sync_regscale_objects_to_jira(
|
|
|
348
349
|
:param int parent_id: Parent record ID in RegScale
|
|
349
350
|
:param str parent_module: Parent record module in RegScale
|
|
350
351
|
:param bool sync_tasks_only: Whether to sync only tasks from Jira
|
|
352
|
+
:param bool use_poams: Whether to create/update the incoming issues as POAMs in RegScale, defaults to False
|
|
351
353
|
"""
|
|
352
354
|
issues_closed = []
|
|
353
355
|
with job_progress:
|
|
@@ -369,21 +371,20 @@ def sync_regscale_objects_to_jira(
|
|
|
369
371
|
progress_task=creating_issues,
|
|
370
372
|
)
|
|
371
373
|
else:
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
),
|
|
385
|
-
thread_count=len(jira_issues),
|
|
374
|
+
app.thread_manager.submit_tasks_from_list(
|
|
375
|
+
create_and_update_regscale_issues,
|
|
376
|
+
jira_issues,
|
|
377
|
+
regscale_objects,
|
|
378
|
+
use_poams,
|
|
379
|
+
sync_attachments,
|
|
380
|
+
jira_client,
|
|
381
|
+
app,
|
|
382
|
+
parent_id,
|
|
383
|
+
parent_module,
|
|
384
|
+
creating_issues,
|
|
385
|
+
job_progress,
|
|
386
386
|
)
|
|
387
|
+
app.thread_manager.execute_and_verify(timeout=ScannerVariables.timeout)
|
|
387
388
|
logger.info(
|
|
388
389
|
"Analyzed %i Jira %s(s), created %i %s(s), updated %i %s(s), and closed %i %s(s) in RegScale.",
|
|
389
390
|
len(jira_issues),
|
|
@@ -409,8 +410,6 @@ def create_jira_client(
|
|
|
409
410
|
:return: JIRA Client
|
|
410
411
|
:rtype: JIRA
|
|
411
412
|
"""
|
|
412
|
-
from regscale.integrations.variables import ScannerVariables
|
|
413
|
-
|
|
414
413
|
url = config["jiraUrl"]
|
|
415
414
|
token = config["jiraApiToken"]
|
|
416
415
|
jira_user = config["jiraUserName"]
|
|
@@ -421,36 +420,6 @@ def create_jira_client(
|
|
|
421
420
|
return JIRA(basic_auth=(jira_user, token), options={"server": url})
|
|
422
421
|
|
|
423
422
|
|
|
424
|
-
def update_regscale_issues(args: Tuple, thread: int) -> None:
|
|
425
|
-
"""
|
|
426
|
-
Function to compare Jira issues and RegScale issues
|
|
427
|
-
|
|
428
|
-
:param Tuple args: Tuple of args to use during the process
|
|
429
|
-
:param int thread: Thread number of current thread
|
|
430
|
-
:rtype: None
|
|
431
|
-
"""
|
|
432
|
-
# set up local variables from the passed args
|
|
433
|
-
(
|
|
434
|
-
regscale_issues,
|
|
435
|
-
task,
|
|
436
|
-
) = args
|
|
437
|
-
# find which records should be executed by the current thread
|
|
438
|
-
threads = thread_assignment(thread=thread, total_items=len(regscale_issues))
|
|
439
|
-
# iterate through the thread assignment items and process them
|
|
440
|
-
for i in range(len(threads)):
|
|
441
|
-
# set the issue for the thread for later use in the function
|
|
442
|
-
issue = regscale_issues[threads[i]]
|
|
443
|
-
# update the issue in RegScale
|
|
444
|
-
issue.save()
|
|
445
|
-
logger.debug(
|
|
446
|
-
"RegScale Issue %i was updated with the Jira link.",
|
|
447
|
-
issue.id,
|
|
448
|
-
)
|
|
449
|
-
update_counter.append(issue)
|
|
450
|
-
# update progress bar
|
|
451
|
-
job_progress.update(task, advance=1)
|
|
452
|
-
|
|
453
|
-
|
|
454
423
|
def convert_task_status(name: str) -> str:
|
|
455
424
|
"""
|
|
456
425
|
Convert the task status from Jira to RegScale
|
|
@@ -494,7 +463,7 @@ def create_regscale_task_from_jira(config: dict, jira_issue: jiraIssue, parent_i
|
|
|
494
463
|
date_closed = status_change_date
|
|
495
464
|
percent_complete = 100
|
|
496
465
|
|
|
497
|
-
|
|
466
|
+
task = Task(
|
|
498
467
|
title=title,
|
|
499
468
|
status=status,
|
|
500
469
|
description=description,
|
|
@@ -507,6 +476,13 @@ def create_regscale_task_from_jira(config: dict, jira_issue: jiraIssue, parent_i
|
|
|
507
476
|
extra_data={"jiraIssue": jira_issue}, # type: ignore
|
|
508
477
|
)
|
|
509
478
|
|
|
479
|
+
# Apply custom field mappings from Jira to RegScale
|
|
480
|
+
custom_fields = config.get("jiraCustomFields", {})
|
|
481
|
+
if custom_fields:
|
|
482
|
+
apply_custom_fields_to_regscale_object(task, custom_fields, jira_issue)
|
|
483
|
+
|
|
484
|
+
return task
|
|
485
|
+
|
|
510
486
|
|
|
511
487
|
def check_and_close_tasks(existing_tasks: list[Task], all_jira_titles: set[str]) -> list[Task]:
|
|
512
488
|
"""
|
|
@@ -567,9 +543,11 @@ def process_tasks_for_sync(
|
|
|
567
543
|
jira_task = create_regscale_task_from_jira(config, jira_issue, parent_id, parent_module)
|
|
568
544
|
|
|
569
545
|
# Check if we have a matching task in RegScale
|
|
570
|
-
existing_task
|
|
546
|
+
if existing_task := existing_task_map.get(jira_issue.key):
|
|
547
|
+
# Apply custom field mappings from Jira to RegScale for existing tasks
|
|
548
|
+
if custom_fields := config.get("jiraCustomFields", {}):
|
|
549
|
+
apply_custom_fields_to_regscale_object(existing_task, custom_fields, jira_issue)
|
|
571
550
|
|
|
572
|
-
if existing_task:
|
|
573
551
|
# Check if task is closed in Jira and needs to be closed in regscale
|
|
574
552
|
if jira_task.status in closed_statuses and existing_task.status not in closed_statuses:
|
|
575
553
|
existing_task.status = "Closed"
|
|
@@ -681,64 +659,93 @@ def task_and_attachments_sync(
|
|
|
681
659
|
)
|
|
682
660
|
|
|
683
661
|
|
|
684
|
-
def
|
|
662
|
+
def _create_new_regscale_issue(
|
|
663
|
+
jira_issue: jiraIssue, app: "Application", parent_id: int, parent_module: str, is_poam: Optional[bool] = False
|
|
664
|
+
) -> Optional[Issue]:
|
|
665
|
+
"""
|
|
666
|
+
Create a new RegScale issue from a Jira issue
|
|
667
|
+
|
|
668
|
+
:param jiraIssue jira_issue: The Jira issue to create from
|
|
669
|
+
:param Application app: RegScale application object
|
|
670
|
+
:param int parent_id: Parent record ID in RegScale
|
|
671
|
+
:param str parent_module: Parent record module in RegScale
|
|
672
|
+
:param bool is_poam: Whether to create the issue as a POAM in RegScale, defaults to False
|
|
673
|
+
:return: The created RegScale issue or None if creation failed
|
|
674
|
+
:rtype: Optional[Issue]
|
|
675
|
+
"""
|
|
676
|
+
issue = map_jira_to_regscale_issue(
|
|
677
|
+
jira_issue=jira_issue,
|
|
678
|
+
config=app.config,
|
|
679
|
+
parent_id=parent_id,
|
|
680
|
+
parent_module=parent_module,
|
|
681
|
+
is_poam=is_poam,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if regscale_issue := issue.create():
|
|
685
|
+
logger.debug(
|
|
686
|
+
"Created issue #%i-%s in RegScale.",
|
|
687
|
+
regscale_issue.id,
|
|
688
|
+
regscale_issue.title,
|
|
689
|
+
)
|
|
690
|
+
return regscale_issue
|
|
691
|
+
else:
|
|
692
|
+
logger.warning("Unable to create issue in RegScale.\nIssue: %s", issue.dict())
|
|
693
|
+
return None
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def _apply_custom_fields_and_update_issue(regscale_issue: Issue, app: "Application", jira_issue: jiraIssue) -> None:
|
|
697
|
+
"""
|
|
698
|
+
Apply custom field mappings and update a RegScale issue
|
|
699
|
+
|
|
700
|
+
:param Issue regscale_issue: The RegScale issue to update
|
|
701
|
+
:param Application app: RegScale application object
|
|
702
|
+
:param jiraIssue jira_issue: The Jira issue to get data from
|
|
703
|
+
:rtype: None
|
|
704
|
+
"""
|
|
705
|
+
if custom_fields := app.config.get("jiraCustomFields", {}):
|
|
706
|
+
apply_custom_fields_to_regscale_object(regscale_issue, custom_fields, jira_issue)
|
|
707
|
+
updated_regscale_issues.append(regscale_issue.save())
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def create_and_update_regscale_issues(jira_issue: jiraIssue, *args, **_) -> None:
|
|
685
711
|
"""
|
|
686
712
|
Function to create or update issues in RegScale from Jira
|
|
687
713
|
|
|
688
|
-
:param
|
|
689
|
-
:param
|
|
714
|
+
:param jiraIssue jira_issue: Jira issue to create or update in RegScale
|
|
715
|
+
:param args: Additional arguments
|
|
690
716
|
:rtype: None
|
|
691
717
|
"""
|
|
692
|
-
# set up local variables from the passed args
|
|
693
|
-
(
|
|
718
|
+
# set up local variables from the passed args Tuple
|
|
719
|
+
(regscale_issues, use_poams, add_attachments, jira_client, app, parent_id, parent_module, task, progress) = args
|
|
694
720
|
# find which records should be executed by the current thread
|
|
695
|
-
threads = thread_assignment(thread=thread, total_items=len(jira_issues))
|
|
696
721
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
722
|
+
regscale_issue: Optional[Issue] = next((issue for issue in regscale_issues if issue.jiraId == jira_issue.key), None)
|
|
723
|
+
if regscale_issue:
|
|
724
|
+
regscale_issue.isPoam = use_poams
|
|
725
|
+
|
|
726
|
+
# Process the Jira issue based on its status and existing RegScale issue
|
|
727
|
+
if jira_issue.fields.status.name.lower() == "done" and regscale_issue:
|
|
728
|
+
regscale_issue.status = "Closed"
|
|
729
|
+
regscale_issue.dateCompleted = get_current_datetime()
|
|
730
|
+
_apply_custom_fields_and_update_issue(regscale_issue, app, jira_issue)
|
|
731
|
+
elif regscale_issue:
|
|
732
|
+
_apply_custom_fields_and_update_issue(regscale_issue, app, jira_issue)
|
|
733
|
+
else:
|
|
734
|
+
regscale_issue = _create_new_regscale_issue(jira_issue, app, parent_id, parent_module, use_poams)
|
|
735
|
+
if regscale_issue:
|
|
736
|
+
new_regscale_issues.append(regscale_issue)
|
|
737
|
+
|
|
738
|
+
# Handle attachments if needed
|
|
739
|
+
if add_attachments and regscale_issue and jira_issue.fields.attachment:
|
|
740
|
+
compare_files_for_dupes_and_upload(
|
|
741
|
+
jira_issue=jira_issue,
|
|
742
|
+
regscale_object=regscale_issue,
|
|
743
|
+
jira_client=jira_client,
|
|
744
|
+
api=Api(),
|
|
702
745
|
)
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
regscale_issue.status = "Closed"
|
|
707
|
-
regscale_issue.dateCompleted = get_current_datetime()
|
|
708
|
-
# update the issue in RegScale
|
|
709
|
-
updated_regscale_issues.append(Issue.update_issue(app=app, issue=regscale_issue))
|
|
710
|
-
elif regscale_issue:
|
|
711
|
-
# update the issue in RegScale
|
|
712
|
-
updated_regscale_issues.append(Issue.update_issue(app=app, issue=regscale_issue))
|
|
713
|
-
else:
|
|
714
|
-
# map the jira issue to a RegScale issue object
|
|
715
|
-
issue = map_jira_to_regscale_issue(
|
|
716
|
-
jira_issue=jira_issue,
|
|
717
|
-
config=app.config,
|
|
718
|
-
parent_id=parent_id,
|
|
719
|
-
parent_module=parent_module,
|
|
720
|
-
)
|
|
721
|
-
# create the issue in RegScale
|
|
722
|
-
if regscale_issue := issue.create():
|
|
723
|
-
logger.debug(
|
|
724
|
-
"Created issue #%i-%s in RegScale.",
|
|
725
|
-
regscale_issue.id,
|
|
726
|
-
regscale_issue.title,
|
|
727
|
-
)
|
|
728
|
-
new_regscale_issues.append(regscale_issue)
|
|
729
|
-
else:
|
|
730
|
-
logger.warning("Unable to create issue in RegScale.\nIssue: %s", issue.dict())
|
|
731
|
-
if add_attachments and regscale_issue and jira_issue.fields.attachment:
|
|
732
|
-
# determine which attachments need to be uploaded to prevent duplicates by
|
|
733
|
-
# getting the hashes of all Jira & RegScale attachments
|
|
734
|
-
compare_files_for_dupes_and_upload(
|
|
735
|
-
jira_issue=jira_issue,
|
|
736
|
-
regscale_object=regscale_issue,
|
|
737
|
-
jira_client=jira_client,
|
|
738
|
-
api=Api(),
|
|
739
|
-
)
|
|
740
|
-
# update progress bar
|
|
741
|
-
progress.update(task, advance=1)
|
|
746
|
+
|
|
747
|
+
# update progress bar
|
|
748
|
+
progress.update(task, advance=1)
|
|
742
749
|
|
|
743
750
|
|
|
744
751
|
def sync_regscale_to_jira(
|
|
@@ -749,6 +756,7 @@ def sync_regscale_to_jira(
|
|
|
749
756
|
sync_attachments: bool = True,
|
|
750
757
|
attachments: Optional[dict] = None,
|
|
751
758
|
api: Optional[Api] = None,
|
|
759
|
+
custom_fields: Optional[dict] = None,
|
|
752
760
|
) -> list[Union[Issue, Task]]:
|
|
753
761
|
"""
|
|
754
762
|
Sync issues or tasks from RegScale to Jira
|
|
@@ -760,6 +768,7 @@ def sync_regscale_to_jira(
|
|
|
760
768
|
:param bool sync_attachments: Sync attachments from RegScale to Jira, defaults to True
|
|
761
769
|
:param Optional[dict] attachments: Dict of attachments to sync from RegScale to Jira, defaults to None
|
|
762
770
|
:param Optional[Api] api: API object to download attachments, defaults to None
|
|
771
|
+
:param Optional[dict] custom_fields: Custom field mappings from Jira custom fields to RegScale issue fields, defaults to None
|
|
763
772
|
:return: list of RegScale issues or tasks that need to be updated
|
|
764
773
|
:rtype: list[Union[Issue, Task]]
|
|
765
774
|
"""
|
|
@@ -787,6 +796,7 @@ def sync_regscale_to_jira(
|
|
|
787
796
|
add_attachments=sync_attachments,
|
|
788
797
|
attachments=attachments,
|
|
789
798
|
api=api,
|
|
799
|
+
custom_fields=custom_fields,
|
|
790
800
|
)
|
|
791
801
|
# log progress
|
|
792
802
|
new_issue_counter += 1
|
|
@@ -940,7 +950,9 @@ def save_jira_issues(jira_issues: list[jiraIssue], jira_project: str, jira_issue
|
|
|
940
950
|
)
|
|
941
951
|
|
|
942
952
|
|
|
943
|
-
def map_jira_to_regscale_issue(
|
|
953
|
+
def map_jira_to_regscale_issue(
|
|
954
|
+
jira_issue: jiraIssue, config: dict, parent_id: int, parent_module: str, is_poam: Optional[bool] = False
|
|
955
|
+
) -> Issue:
|
|
944
956
|
"""
|
|
945
957
|
Map Jira issues to RegScale issues
|
|
946
958
|
|
|
@@ -948,6 +960,7 @@ def map_jira_to_regscale_issue(jira_issue: jiraIssue, config: dict, parent_id: i
|
|
|
948
960
|
:param dict config: Application config
|
|
949
961
|
:param int parent_id: Parent record ID in RegScale
|
|
950
962
|
:param str parent_module: Parent record module in RegScale
|
|
963
|
+
:param bool is_poam: Whether to create the issue as a POAM in RegScale
|
|
951
964
|
:return: Issue object of the newly created issue in RegScale
|
|
952
965
|
:rtype: Issue
|
|
953
966
|
"""
|
|
@@ -970,7 +983,14 @@ def map_jira_to_regscale_issue(jira_issue: jiraIssue, config: dict, parent_id: i
|
|
|
970
983
|
parentModule=parent_module,
|
|
971
984
|
dateCreated=get_current_datetime(),
|
|
972
985
|
dateCompleted=(get_current_datetime() if jira_issue.fields.status.name.lower() == "done" else None),
|
|
986
|
+
isPoam=is_poam,
|
|
973
987
|
)
|
|
988
|
+
|
|
989
|
+
# Apply custom field mappings from Jira to RegScale
|
|
990
|
+
custom_fields = config.get("jiraCustomFields", {})
|
|
991
|
+
if custom_fields:
|
|
992
|
+
apply_custom_fields_to_regscale_object(issue, custom_fields, jira_issue)
|
|
993
|
+
|
|
974
994
|
return issue
|
|
975
995
|
|
|
976
996
|
|
|
@@ -1024,6 +1044,7 @@ def create_issue_in_jira(
|
|
|
1024
1044
|
add_attachments: Optional[bool] = True,
|
|
1025
1045
|
attachments: Optional[dict] = None,
|
|
1026
1046
|
api: Optional[Api] = None,
|
|
1047
|
+
custom_fields: Optional[dict] = None,
|
|
1027
1048
|
) -> jiraIssue:
|
|
1028
1049
|
"""
|
|
1029
1050
|
Create a new issue in Jira
|
|
@@ -1035,6 +1056,7 @@ def create_issue_in_jira(
|
|
|
1035
1056
|
:param Optional[bool] add_attachments: Whether to add attachments to new issue, defaults to true
|
|
1036
1057
|
:param Optional[dict] attachments: Dictionary containing attachments, defaults to None
|
|
1037
1058
|
:param Optional[Api] api: API object to download attachments, defaults to None
|
|
1059
|
+
:param Optional[dict] custom_fields: Custom field mappings from Jira custom fields to RegScale issue fields, defaults to None
|
|
1038
1060
|
:return: Newly created issue in Jira
|
|
1039
1061
|
:rtype: jiraIssue
|
|
1040
1062
|
"""
|
|
@@ -1050,6 +1072,11 @@ def create_issue_in_jira(
|
|
|
1050
1072
|
issuetype=issue_type,
|
|
1051
1073
|
)
|
|
1052
1074
|
logger.debug("Jira issue created: %s", new_issue.key)
|
|
1075
|
+
|
|
1076
|
+
# Apply custom field mappings if provided
|
|
1077
|
+
if custom_fields:
|
|
1078
|
+
apply_custom_fields_to_jira_issue(new_issue, custom_fields, regscale_object)
|
|
1079
|
+
|
|
1053
1080
|
# add a comment to the new Jira issue
|
|
1054
1081
|
logger.debug("Adding comment to Jira issue: %s", new_issue.key)
|
|
1055
1082
|
_ = jira_client.add_comment(
|
|
@@ -1309,3 +1336,217 @@ def download_regscale_attachments_to_directory(
|
|
|
1309
1336
|
)
|
|
1310
1337
|
)
|
|
1311
1338
|
return jira_dir, regscale_dir
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
def apply_custom_fields_to_jira_issue(
|
|
1342
|
+
jira_issue: jiraIssue, custom_fields: dict, regscale_object: Union[Issue, Task]
|
|
1343
|
+
) -> None:
|
|
1344
|
+
"""
|
|
1345
|
+
Apply custom field mappings to a Jira issue based on RegScale object attributes (RegScale -> Jira)
|
|
1346
|
+
|
|
1347
|
+
:param jiraIssue jira_issue: Jira issue to apply custom fields to
|
|
1348
|
+
:param dict custom_fields: Dictionary mapping Jira custom field names to RegScale attribute names
|
|
1349
|
+
:param Union[Issue, Task] regscale_object: RegScale object to get attribute values from
|
|
1350
|
+
:rtype: None
|
|
1351
|
+
"""
|
|
1352
|
+
if not custom_fields:
|
|
1353
|
+
return
|
|
1354
|
+
|
|
1355
|
+
try:
|
|
1356
|
+
# Convert RegScale object to dictionary for easier attribute access
|
|
1357
|
+
if hasattr(regscale_object, "model_dump"):
|
|
1358
|
+
regscale_dict = regscale_object.model_dump()
|
|
1359
|
+
elif hasattr(regscale_object, "dict"):
|
|
1360
|
+
regscale_dict = regscale_object.dict()
|
|
1361
|
+
else:
|
|
1362
|
+
regscale_dict = regscale_object.__dict__
|
|
1363
|
+
|
|
1364
|
+
# Build custom fields dictionary for Jira update
|
|
1365
|
+
jira_custom_fields = {}
|
|
1366
|
+
|
|
1367
|
+
for jira_field_name, regscale_field_name in custom_fields.items():
|
|
1368
|
+
try:
|
|
1369
|
+
# Get the value from RegScale object
|
|
1370
|
+
field_value = regscale_dict.get(regscale_field_name)
|
|
1371
|
+
|
|
1372
|
+
if field_value is not None:
|
|
1373
|
+
jira_custom_fields[jira_field_name] = field_value
|
|
1374
|
+
logger.debug(
|
|
1375
|
+
"Mapped custom field %s (RegScale: %s) = %s for Jira issue %s",
|
|
1376
|
+
jira_field_name,
|
|
1377
|
+
regscale_field_name,
|
|
1378
|
+
field_value,
|
|
1379
|
+
jira_issue.key,
|
|
1380
|
+
)
|
|
1381
|
+
else:
|
|
1382
|
+
logger.debug(
|
|
1383
|
+
"Custom field %s (RegScale: %s) has no value, skipping for Jira issue %s",
|
|
1384
|
+
jira_field_name,
|
|
1385
|
+
regscale_field_name,
|
|
1386
|
+
jira_issue.key,
|
|
1387
|
+
)
|
|
1388
|
+
except Exception as e:
|
|
1389
|
+
logger.warning(
|
|
1390
|
+
"Unable to set custom field %s (RegScale: %s) for Jira issue %s: %s",
|
|
1391
|
+
jira_field_name,
|
|
1392
|
+
regscale_field_name,
|
|
1393
|
+
jira_issue.key,
|
|
1394
|
+
str(e),
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
# Update the Jira issue with custom fields if any were found
|
|
1398
|
+
if jira_custom_fields:
|
|
1399
|
+
jira_issue.update(fields=jira_custom_fields)
|
|
1400
|
+
logger.info(
|
|
1401
|
+
"Applied %d custom fields to Jira issue %s: %s",
|
|
1402
|
+
len(jira_custom_fields),
|
|
1403
|
+
jira_issue.key,
|
|
1404
|
+
list(jira_custom_fields.keys()),
|
|
1405
|
+
)
|
|
1406
|
+
else:
|
|
1407
|
+
logger.debug("No custom field values found for Jira issue %s", jira_issue.key)
|
|
1408
|
+
|
|
1409
|
+
except Exception as e:
|
|
1410
|
+
logger.warning(
|
|
1411
|
+
"Error applying custom fields to Jira issue %s: %s",
|
|
1412
|
+
jira_issue.key,
|
|
1413
|
+
str(e),
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
|
|
1417
|
+
def _get_jira_field_value(jira_issue: jiraIssue, jira_field_name: str) -> Optional[Any]:
|
|
1418
|
+
"""
|
|
1419
|
+
Get a custom field value from a Jira issue
|
|
1420
|
+
|
|
1421
|
+
:param jiraIssue jira_issue: The Jira issue to get the field value from
|
|
1422
|
+
:param str jira_field_name: The name of the field to retrieve
|
|
1423
|
+
:return: The field value or None if not found
|
|
1424
|
+
:rtype: Optional[Any]
|
|
1425
|
+
"""
|
|
1426
|
+
# Try to access the custom field from the Jira issue
|
|
1427
|
+
if hasattr(jira_issue.fields, jira_field_name):
|
|
1428
|
+
return getattr(jira_issue.fields, jira_field_name)
|
|
1429
|
+
|
|
1430
|
+
# Try accessing through raw fields (for custom fields)
|
|
1431
|
+
if hasattr(jira_issue.fields, "raw") and jira_field_name in jira_issue.fields.raw:
|
|
1432
|
+
return jira_issue.fields.raw[jira_field_name]
|
|
1433
|
+
|
|
1434
|
+
return None
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
def _set_regscale_field_value(
|
|
1438
|
+
regscale_object: Union[Issue, Task],
|
|
1439
|
+
regscale_field_name: str,
|
|
1440
|
+
jira_field_value: Any,
|
|
1441
|
+
jira_field_name: str,
|
|
1442
|
+
jira_issue: jiraIssue,
|
|
1443
|
+
) -> bool:
|
|
1444
|
+
"""
|
|
1445
|
+
Set a field value on a RegScale object
|
|
1446
|
+
|
|
1447
|
+
:param Union[Issue, Task] regscale_object: The RegScale object to set the field on
|
|
1448
|
+
:param str regscale_field_name: The name of the field to set
|
|
1449
|
+
:param Any jira_field_value: The value to set
|
|
1450
|
+
:param str jira_field_name: The Jira field name for logging
|
|
1451
|
+
:param jiraIssue jira_issue: The Jira issue for logging
|
|
1452
|
+
:return: True if the field was set successfully, False otherwise
|
|
1453
|
+
:rtype: bool
|
|
1454
|
+
"""
|
|
1455
|
+
if hasattr(regscale_object, regscale_field_name):
|
|
1456
|
+
setattr(regscale_object, regscale_field_name, jira_field_value)
|
|
1457
|
+
logger.debug(
|
|
1458
|
+
"Mapped custom field %s (Jira: %s) = %s for RegScale %s #%s from Jira issue %s",
|
|
1459
|
+
regscale_field_name,
|
|
1460
|
+
jira_field_name,
|
|
1461
|
+
jira_field_value,
|
|
1462
|
+
regscale_object.get_module_string().title(),
|
|
1463
|
+
regscale_object.id,
|
|
1464
|
+
jira_issue.key,
|
|
1465
|
+
)
|
|
1466
|
+
return True
|
|
1467
|
+
else:
|
|
1468
|
+
logger.debug(
|
|
1469
|
+
"RegScale object does not have field %s, skipping custom field %s from Jira issue %s",
|
|
1470
|
+
regscale_field_name,
|
|
1471
|
+
jira_field_name,
|
|
1472
|
+
jira_issue.key,
|
|
1473
|
+
)
|
|
1474
|
+
return False
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
def _process_single_custom_field(
|
|
1478
|
+
jira_field_name: str, regscale_field_name: str, regscale_object: Union[Issue, Task], jira_issue: jiraIssue
|
|
1479
|
+
) -> bool:
|
|
1480
|
+
"""
|
|
1481
|
+
Process a single custom field mapping from Jira to RegScale
|
|
1482
|
+
|
|
1483
|
+
:param str jira_field_name: The Jira field name
|
|
1484
|
+
:param str regscale_field_name: The RegScale field name
|
|
1485
|
+
:param Union[Issue, Task] regscale_object: The RegScale object to update
|
|
1486
|
+
:param jiraIssue jira_issue: The Jira issue to get data from
|
|
1487
|
+
:return: True if the field was processed successfully, False otherwise
|
|
1488
|
+
:rtype: bool
|
|
1489
|
+
"""
|
|
1490
|
+
try:
|
|
1491
|
+
jira_field_value = _get_jira_field_value(jira_issue, jira_field_name)
|
|
1492
|
+
|
|
1493
|
+
if jira_field_value is not None:
|
|
1494
|
+
return _set_regscale_field_value(
|
|
1495
|
+
regscale_object, regscale_field_name, jira_field_value, jira_field_name, jira_issue
|
|
1496
|
+
)
|
|
1497
|
+
else:
|
|
1498
|
+
logger.debug(
|
|
1499
|
+
"Custom field %s has no value in Jira issue %s, skipping",
|
|
1500
|
+
jira_field_name,
|
|
1501
|
+
jira_issue.key,
|
|
1502
|
+
)
|
|
1503
|
+
return False
|
|
1504
|
+
except Exception as e:
|
|
1505
|
+
logger.warning(
|
|
1506
|
+
"Unable to set custom field %s (Jira: %s) for RegScale %s #%s: %s",
|
|
1507
|
+
regscale_field_name,
|
|
1508
|
+
jira_field_name,
|
|
1509
|
+
regscale_object.get_module_string().title(),
|
|
1510
|
+
regscale_object.id,
|
|
1511
|
+
str(e),
|
|
1512
|
+
)
|
|
1513
|
+
return False
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
def apply_custom_fields_to_regscale_object(
|
|
1517
|
+
regscale_object: Union[Issue, Task], custom_fields: dict, jira_issue: jiraIssue
|
|
1518
|
+
) -> None:
|
|
1519
|
+
"""
|
|
1520
|
+
Apply custom field mappings to a RegScale object based on Jira issue custom fields (Jira -> RegScale)
|
|
1521
|
+
|
|
1522
|
+
:param Union[Issue, Task] regscale_object: RegScale object to apply custom fields to
|
|
1523
|
+
:param dict custom_fields: Dictionary mapping Jira custom field names to RegScale attribute names
|
|
1524
|
+
:param jiraIssue jira_issue: Jira issue to get custom field values from
|
|
1525
|
+
:rtype: None
|
|
1526
|
+
"""
|
|
1527
|
+
if not custom_fields:
|
|
1528
|
+
return
|
|
1529
|
+
|
|
1530
|
+
try:
|
|
1531
|
+
fields_updated = False
|
|
1532
|
+
|
|
1533
|
+
for jira_field_name, regscale_field_name in custom_fields.items():
|
|
1534
|
+
if _process_single_custom_field(jira_field_name, regscale_field_name, regscale_object, jira_issue):
|
|
1535
|
+
fields_updated = True
|
|
1536
|
+
|
|
1537
|
+
if fields_updated:
|
|
1538
|
+
logger.info(
|
|
1539
|
+
"Applied custom fields from Jira issue %s to RegScale %s #%s",
|
|
1540
|
+
jira_issue.key,
|
|
1541
|
+
regscale_object.get_module_string().title(),
|
|
1542
|
+
regscale_object.id,
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
except Exception as e:
|
|
1546
|
+
logger.warning(
|
|
1547
|
+
"Error applying custom fields from Jira issue %s to RegScale %s #%s: %s",
|
|
1548
|
+
jira_issue.key,
|
|
1549
|
+
regscale_object.get_module_string().title(),
|
|
1550
|
+
regscale_object.id,
|
|
1551
|
+
str(e),
|
|
1552
|
+
)
|