devsecops-engine-tools 1.98.1__py3-none-any.whl → 1.99.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 devsecops-engine-tools might be problematic. Click here for more details.

Files changed (24) hide show
  1. devsecops_engine_tools/engine_integrations/src/applications/runner_engine_integrations.py +62 -1
  2. devsecops_engine_tools/engine_integrations/src/domain/usecases/handle_integrations.py +12 -0
  3. devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_adapters/trivy_tool/trivy_manager_scan.py +18 -6
  4. devsecops_engine_tools/engine_utilities/copacetic/__init__.py +0 -0
  5. devsecops_engine_tools/engine_utilities/copacetic/src/__init__.py +0 -0
  6. devsecops_engine_tools/engine_utilities/copacetic/src/applications/__init__.py +0 -0
  7. devsecops_engine_tools/engine_utilities/copacetic/src/applications/runner_copacetic.py +40 -0
  8. devsecops_engine_tools/engine_utilities/copacetic/src/domain/__init__.py +0 -0
  9. devsecops_engine_tools/engine_utilities/copacetic/src/domain/usecases/__init__.py +0 -0
  10. devsecops_engine_tools/engine_utilities/copacetic/src/domain/usecases/copacetic.py +190 -0
  11. devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/__init__.py +0 -0
  12. devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/driven_adapters/__init__.py +0 -0
  13. devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/driven_adapters/copacetic/__init__.py +0 -0
  14. devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/driven_adapters/copacetic/copacetic_adapter.py +309 -0
  15. devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/entry_points/__init__.py +0 -0
  16. devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/entry_points/entry_point_copacetic.py +78 -0
  17. devsecops_engine_tools/engine_utilities/trivy_utils/infrastructure/driven_adapters/trivy_deserialize_output.py +1 -1
  18. devsecops_engine_tools/engine_utilities/utils/logger_info.py +1 -1
  19. devsecops_engine_tools/version.py +1 -1
  20. {devsecops_engine_tools-1.98.1.dist-info → devsecops_engine_tools-1.99.0.dist-info}/METADATA +1 -1
  21. {devsecops_engine_tools-1.98.1.dist-info → devsecops_engine_tools-1.99.0.dist-info}/RECORD +24 -11
  22. {devsecops_engine_tools-1.98.1.dist-info → devsecops_engine_tools-1.99.0.dist-info}/WHEEL +0 -0
  23. {devsecops_engine_tools-1.98.1.dist-info → devsecops_engine_tools-1.99.0.dist-info}/entry_points.txt +0 -0
  24. {devsecops_engine_tools-1.98.1.dist-info → devsecops_engine_tools-1.99.0.dist-info}/top_level.txt +0 -0
@@ -29,11 +29,33 @@ from devsecops_engine_tools.engine_utilities import settings
29
29
 
30
30
  logger = MyLogger.__call__(**settings.SETTING_LOGGER).get_logger()
31
31
 
32
+ def validate_integration_requirements(args):
33
+ integration = args.get("integration")
34
+ missing_args = []
35
+
36
+ if integration == "report_sonar":
37
+ if not args.get("sonar_url"):
38
+ missing_args.append("--sonar_url")
39
+
40
+ elif integration == "copacetic":
41
+ if not args.get("image"):
42
+ missing_args.append("--image")
43
+
44
+ if not args.get("vulnerability_report") and not args.get("platform"):
45
+ missing_args.append("--vulnerability_report or --platform")
46
+
47
+ if missing_args:
48
+ error_msg = f"Missing required arguments for {integration} integration: {', '.join(missing_args)}"
49
+ return False, error_msg
50
+
51
+ return True, None
52
+
32
53
  def get_inputs_from_cli(args):
33
54
  parser = argparse.ArgumentParser()
55
+ # General flags
34
56
  parser.add_argument(
35
57
  "--integration",
36
- choices=["report_sonar"],
58
+ choices=["report_sonar", "copacetic"],
37
59
  type=str,
38
60
  required=True,
39
61
  help="Name of the integration to run",
@@ -109,6 +131,35 @@ def get_inputs_from_cli(args):
109
131
  required=False,
110
132
  help="Token to access sonar server",
111
133
  )
134
+ # copacetic flags
135
+ parser.add_argument(
136
+ "--image",
137
+ required=False,
138
+ help="Container image to patch with Copacetic",
139
+ )
140
+ parser.add_argument(
141
+ "--vulnerability_report",
142
+ required=False,
143
+ help="Path to vulnerability report file for Copacetic",
144
+ )
145
+ parser.add_argument(
146
+ "--patch_format",
147
+ choices=["trivy", "grype"],
148
+ type=str,
149
+ required=False,
150
+ default="trivy",
151
+ help="Vulnerability report format for Copacetic (default: trivy)",
152
+ )
153
+ parser.add_argument(
154
+ "--output_image",
155
+ required=False,
156
+ help="Output tag name for patched image",
157
+ )
158
+ parser.add_argument(
159
+ "--platform",
160
+ required=False,
161
+ help="Target(s) platform(s) for multi-arch images when no report directory is provided",
162
+ )
112
163
 
113
164
  args = parser.parse_args()
114
165
  return {
@@ -124,6 +175,11 @@ def get_inputs_from_cli(args):
124
175
  "token_cmdb": args.token_cmdb,
125
176
  "token_vulnerability_management": args.token_vulnerability_management,
126
177
  "token_sonar": args.token_sonar,
178
+ "image": args.image,
179
+ "vulnerability_report": args.vulnerability_report,
180
+ "patch_format": args.patch_format,
181
+ "output_image": args.output_image,
182
+ "platform": args.platform
127
183
  }
128
184
 
129
185
  def runner_engine_integrations():
@@ -132,6 +188,11 @@ def runner_engine_integrations():
132
188
  if not args["remote_config_source"]:
133
189
  args["remote_config_source"] = args["platform_devops"]
134
190
 
191
+ is_valid, error_message = validate_integration_requirements(args)
192
+ if not is_valid:
193
+ logger.error(f"Error: {error_message}")
194
+ sys.exit(1)
195
+
135
196
  vulnerability_management_gateway = DefectDojoPlatform()
136
197
  secrets_manager_gateway = SecretsManager()
137
198
  devops_platform_gateway = {
@@ -13,6 +13,9 @@ from devsecops_engine_tools.engine_core.src.domain.model.gateway.metrics_manager
13
13
  from devsecops_engine_tools.engine_utilities.sonarqube.src.applications.runner_report_sonar import (
14
14
  runner_report_sonar
15
15
  )
16
+ from devsecops_engine_tools.engine_utilities.copacetic.src.applications.runner_copacetic import (
17
+ runner_copacetic
18
+ )
16
19
 
17
20
  class Integrations():
18
21
  def __init__(
@@ -40,3 +43,12 @@ class Integrations():
40
43
  metrics_manager_gateway=self.metrics_manager_gateway,
41
44
  args=args,
42
45
  )
46
+ elif integration == "copacetic":
47
+ return runner_copacetic(
48
+ vulnerability_management_gateway=self.vulnerability_management_gateway,
49
+ secrets_manager_gateway=self.secrets_manager_gateway,
50
+ devops_platform_gateway=self.devops_platform_gateway,
51
+ remote_config_source_gateway=self.remote_config_source_gateway,
52
+ metrics_manager_gateway=self.metrics_manager_gateway,
53
+ args=args,
54
+ )
@@ -16,7 +16,7 @@ logger = MyLogger.__call__(**settings.SETTING_LOGGER).get_logger()
16
16
 
17
17
  class TrivyScan(ToolGateway):
18
18
 
19
- def scan_image(self, prefix, image_name, result_file, base_image, is_compressed_file=False):
19
+ def scan_image(self, prefix, image_name, result_file, base_image, vuln_type, ignore_unfixed=False, is_compressed_file=False):
20
20
  command = [
21
21
  prefix,
22
22
  "--scanners",
@@ -25,7 +25,12 @@ class TrivyScan(ToolGateway):
25
25
  "json",
26
26
  "-o",
27
27
  result_file,
28
+ "--pkg-types",
29
+ vuln_type
28
30
  ]
31
+
32
+ if ignore_unfixed:
33
+ command.append("--ignore-unfixed")
29
34
 
30
35
  if is_compressed_file:
31
36
  command.extend(["--quiet", "image", "--input", image_name])
@@ -47,7 +52,7 @@ class TrivyScan(ToolGateway):
47
52
  except Exception as e:
48
53
  logger.error(f"Error during image scan of {image_name}: {e}")
49
54
 
50
- def _generate_sbom(self, prefix, image_name, remoteconfig, is_compressed_file=False):
55
+ def _generate_sbom(self, prefix, image_name, remoteconfig, vuln_type, ignore_unfixed=False, is_compressed_file=False):
51
56
  result_sbom = f"{image_name.replace('/', '_')}_SBOM.json"
52
57
  command = [
53
58
  prefix,
@@ -55,8 +60,12 @@ class TrivyScan(ToolGateway):
55
60
  "--format",
56
61
  remoteconfig["TRIVY"]["SBOM_FORMAT"],
57
62
  "--output",
58
- result_sbom
63
+ result_sbom,
64
+ "--pkg-types",
65
+ vuln_type
59
66
  ]
67
+ if ignore_unfixed:
68
+ command.append("--ignore-unfixed")
60
69
  if is_compressed_file:
61
70
  command.extend(["--quiet", "--input", image_name])
62
71
  else:
@@ -77,7 +86,10 @@ class TrivyScan(ToolGateway):
77
86
  logger.error(f"Error generating SBOM: {e}")
78
87
 
79
88
  def run_tool_container_sca(self, remoteconfig, secret_tool, token_engine_container, image_name, result_file, base_image, exclusions, generate_sbom, is_compressed_file=False):
80
- trivy_version = remoteconfig["TRIVY"]["TRIVY_VERSION"]
89
+ trivy_version = remoteconfig["TRIVY"]["TRIVY_VERSION"]
90
+ vuln_type = remoteconfig["TRIVY"].get("VULN_TYPE", "all").lower()
91
+ vuln_type = vuln_type if vuln_type in ["os", "library"] else "os,library"
92
+ ignore_unfixed = remoteconfig["TRIVY"].get("IGNORE_UNFIXED", False)
81
93
  command_prefix = TrivyManagerScanUtils().identify_os_and_install(trivy_version)
82
94
  sbom_components = None
83
95
 
@@ -85,9 +97,9 @@ class TrivyScan(ToolGateway):
85
97
  return None
86
98
 
87
99
  image_scanned = (
88
- self.scan_image(command_prefix, image_name, result_file, base_image, is_compressed_file)
100
+ self.scan_image(command_prefix, image_name, result_file, base_image, vuln_type, ignore_unfixed, is_compressed_file)
89
101
  )
90
102
  if generate_sbom:
91
- sbom_components = self._generate_sbom(command_prefix, image_name, remoteconfig, is_compressed_file)
103
+ sbom_components = self._generate_sbom(command_prefix, image_name, remoteconfig, vuln_type, ignore_unfixed, is_compressed_file)
92
104
 
93
105
  return image_scanned, sbom_components
@@ -0,0 +1,40 @@
1
+ from devsecops_engine_tools.engine_utilities.copacetic.src.infrastructure.driven_adapters.copacetic.copacetic_adapter import(
2
+ CopaceticAdapter
3
+ )
4
+ from devsecops_engine_tools.engine_utilities.copacetic.src.infrastructure.entry_points.entry_point_copacetic import (
5
+ init_copacetic
6
+ )
7
+ from devsecops_engine_tools.engine_utilities.utils.logger_info import MyLogger
8
+ from devsecops_engine_tools.engine_utilities import settings
9
+
10
+ logger = MyLogger.__call__(**settings.SETTING_LOGGER).get_logger()
11
+
12
+ def runner_copacetic(
13
+ vulnerability_management_gateway,
14
+ secrets_manager_gateway,
15
+ devops_platform_gateway,
16
+ remote_config_source_gateway,
17
+ metrics_manager_gateway,
18
+ args
19
+ ):
20
+ try:
21
+ copacetic_gateway = CopaceticAdapter()
22
+
23
+ init_copacetic(
24
+ vulnerability_management_gateway=vulnerability_management_gateway,
25
+ secrets_manager_gateway=secrets_manager_gateway,
26
+ devops_platform_gateway=devops_platform_gateway,
27
+ remote_config_source_gateway=remote_config_source_gateway,
28
+ copacetic_gateway=copacetic_gateway,
29
+ metrics_manager_gateway=metrics_manager_gateway,
30
+ args=args,
31
+ )
32
+
33
+ except Exception as e:
34
+ logger.error("Error copacetic: {0} ".format(str(e)))
35
+ print(
36
+ devops_platform_gateway.message(
37
+ "error", "Error copacetic: {0} ".format(str(e))
38
+ )
39
+ )
40
+ print(devops_platform_gateway.result_pipeline("failed"))
@@ -0,0 +1,190 @@
1
+ from devsecops_engine_tools.engine_core.src.domain.model.gateway.vulnerability_management_gateway import (
2
+ VulnerabilityManagementGateway
3
+ )
4
+ from devsecops_engine_tools.engine_core.src.domain.model.gateway.secrets_manager_gateway import (
5
+ SecretsManagerGateway
6
+ )
7
+ from devsecops_engine_tools.engine_core.src.domain.model.gateway.devops_platform_gateway import (
8
+ DevopsPlatformGateway
9
+ )
10
+ from devsecops_engine_tools.engine_core.src.domain.model.input_core import InputCore
11
+ from devsecops_engine_tools.engine_utilities.utils.logger_info import MyLogger
12
+ from devsecops_engine_tools.engine_utilities import settings
13
+ import shutil
14
+
15
+ logger = MyLogger.__call__(**settings.SETTING_LOGGER).get_logger()
16
+
17
+
18
+ class Copacetic:
19
+ def __init__(
20
+ self,
21
+ vulnerability_management_gateway: VulnerabilityManagementGateway,
22
+ secrets_manager_gateway: SecretsManagerGateway,
23
+ devops_platform_gateway: DevopsPlatformGateway,
24
+ remote_config_source_gateway: DevopsPlatformGateway,
25
+ copacetic_gateway,
26
+ ):
27
+ self.vulnerability_management_gateway = vulnerability_management_gateway
28
+ self.secrets_manager_gateway = secrets_manager_gateway
29
+ self.devops_platform_gateway = devops_platform_gateway
30
+ self.remote_config_source_gateway = remote_config_source_gateway
31
+ self.copacetic_gateway = copacetic_gateway
32
+
33
+ def process(self, args):
34
+ try:
35
+ copacetic_config = self.remote_config_source_gateway.get_remote_config(
36
+ args["remote_config_repo"], "/engine_integrations/copacetic/ConfigTool.json", args["remote_config_branch"]
37
+ )
38
+
39
+ image = args.get("image")
40
+ vulnerability_report = args.get("vulnerability_report")
41
+ patch_format = args.get("patch_format", "trivy")
42
+
43
+ if not image:
44
+ raise ValueError("Image is required for Copacetic patching")
45
+
46
+ logger.info(f"Starting Copacetic patching for image: {image}")
47
+
48
+ image_info = {}
49
+ if hasattr(self.copacetic_gateway, 'get_image_info'):
50
+ image_info = self.copacetic_gateway.get_image_info(image)
51
+ if not image_info.get("exists", False):
52
+ logger.info(f"Image {image} may not exist locally. Copacetic will attempt to pull it.")
53
+
54
+ patch_result = self.copacetic_gateway.patch_image(
55
+ image=image,
56
+ vulnerability_report=vulnerability_report,
57
+ output_image=args.get("output_image"),
58
+ patch_format=patch_format,
59
+ config=copacetic_config,
60
+ work_folder=self.devops_platform_gateway.get_variable("path_directory"),
61
+ platform=args.get("platform")
62
+ )
63
+
64
+ if patch_result["success"]:
65
+ logger.info("Copacetic patching completed successfully")
66
+
67
+ if image_info.get("exists", False):
68
+ patch_result["original_image_info"] = {
69
+ "architecture": image_info.get("architecture"),
70
+ "os": image_info.get("os"),
71
+ "size": image_info.get("size"),
72
+ "layers": image_info.get("layers")
73
+ }
74
+
75
+ self._print_results_table(patch_result)
76
+ else:
77
+ detailed_error = f"Copacetic patching failed for {image}: {patch_result.get('error', 'Unknown error')}"
78
+ if patch_result.get("copa_error"):
79
+ detailed_error += f"\nCopa stderr: {patch_result['copa_error']}"
80
+
81
+ print(
82
+ self.devops_platform_gateway.message("error", detailed_error)
83
+ )
84
+
85
+ return InputCore(
86
+ totalized_exclusions=[],
87
+ threshold_defined=None,
88
+ path_file_results=patch_result.get("output_file", ""),
89
+ custom_message_break_build=f"Copacetic patching completed for {image}",
90
+ scope_pipeline="",
91
+ scope_service="",
92
+ stage_pipeline=self.devops_platform_gateway.get_variable("stage")
93
+ )
94
+
95
+ except Exception as e:
96
+ logger.error(f"Error in Copacetic process: {str(e)}")
97
+ print(
98
+ self.devops_platform_gateway.message(
99
+ "error",
100
+ f"Error in Copacetic process: {str(e)}"
101
+ )
102
+ )
103
+
104
+ def _print_results_table(self, summary):
105
+ try:
106
+ terminal_width = min(shutil.get_terminal_size().columns, 100)
107
+ except:
108
+ terminal_width = 100
109
+
110
+ logger.info(f"\n{'='*terminal_width}")
111
+ title = " COPACETIC PATCHING RESULTS "
112
+ padding = (terminal_width - len(title)) // 2
113
+ logger.info(f"{' '*padding}{title}{' '*padding}")
114
+ logger.info(f"{'='*terminal_width}")
115
+
116
+ logger.info("\n IMAGE INFORMATION:")
117
+ logger.info(f" Original Image: {summary['original_image']}")
118
+ logger.info(f" Patched Image: {summary['patched_image']}")
119
+
120
+ logger.info("\n PATCHING STATISTICS:")
121
+ vuln_count = summary.get('vulnerabilities_patched', 0)
122
+ pkg_count = summary.get('packages_updated', 0)
123
+ vex_generated = summary.get('vex_file_generated', False)
124
+
125
+ logger.info(f" Vulnerabilities Patched: {vuln_count}")
126
+ logger.info(f" Packages Updated: {pkg_count}")
127
+ logger.info(f" VEX Report Generated: {'Yes' if vex_generated else 'No'}")
128
+
129
+ if not vex_generated:
130
+ logger.info(" Note: VEX report not generated (no vulnerability report provided)")
131
+
132
+ platforms = summary.get('platforms_processed', [])
133
+ if platforms:
134
+ logger.info(f" Platforms Processed: {', '.join(platforms)}")
135
+
136
+ if summary.get('original_image_info'):
137
+ info = summary['original_image_info']
138
+ logger.info("\n IMAGE DETAILS:")
139
+ logger.info(f" Architecture: {info.get('architecture', 'N/A')}")
140
+ logger.info(f" OS: {info.get('os', 'N/A')}")
141
+ logger.info(f" Size: {info.get('size', 'N/A')}")
142
+ logger.info(f" Layers: {info.get('layers', 'N/A')}")
143
+
144
+ patch_details = summary.get('patch_details', [])
145
+ if patch_details:
146
+ logger.info("\n PATCH DETAILS:")
147
+
148
+ for i, detail in enumerate(patch_details, 1):
149
+ if isinstance(detail, dict):
150
+ cve = detail.get('vulnerability', detail.get('id', f'PATCH-{i}'))
151
+ logger.info(f" {cve}")
152
+
153
+ packages = detail.get('packages', [])
154
+ if not packages:
155
+ single_package = detail.get('package', detail.get('component'))
156
+ if single_package:
157
+ packages = [single_package]
158
+
159
+ for pkg in packages:
160
+ if isinstance(pkg, dict):
161
+ pkg_name = pkg.get('name', pkg.get('package', 'Unknown'))
162
+ version = pkg.get('version', pkg.get('fixed_version', ''))
163
+ if version:
164
+ logger.info(f" {pkg_name} (v{version})")
165
+ else:
166
+ logger.info(f" {pkg_name}")
167
+ else:
168
+ logger.info(f" {pkg}")
169
+
170
+ if not packages:
171
+ logger.info(" No package information available")
172
+
173
+ else:
174
+ logger.info(f" {detail}")
175
+
176
+ if i < len(patch_details):
177
+ logger.info("")
178
+
179
+ logger.info("\n SUMMARY:")
180
+ vex_generated = summary.get('vex_file_generated', False)
181
+
182
+ if vuln_count > 0:
183
+ logger.info(f" Status: SUCCESS - {vuln_count} vulnerabilities were patched")
184
+ elif vex_generated:
185
+ logger.info(" Status: COMPLETED - No vulnerabilities found to patch")
186
+ else:
187
+ logger.info(" Status: COMPLETED - Image patched successfully without vulnerability report")
188
+ logger.info(" Use --vulnerability_report to generate detailed VEX output")
189
+
190
+ logger.info(f"{'='*terminal_width}\n")
@@ -0,0 +1,309 @@
1
+ import subprocess
2
+ import os
3
+ import json
4
+ import platform
5
+ import requests
6
+ import tarfile
7
+ import tempfile
8
+ import docker
9
+ from typing import Dict, Optional
10
+ from devsecops_engine_tools.engine_utilities.utils.logger_info import MyLogger
11
+ from devsecops_engine_tools.engine_utilities import settings
12
+
13
+ logger = MyLogger.__call__(**settings.SETTING_LOGGER).get_logger()
14
+
15
+
16
+ class CopaceticAdapter:
17
+ def __init__(self, config: Optional[Dict] = None):
18
+ self.config = config or {}
19
+
20
+ def install_tool(self, version, path="."):
21
+ try:
22
+ installed = subprocess.run(
23
+ ["which", "copa"],
24
+ stdout=subprocess.PIPE,
25
+ stderr=subprocess.PIPE,
26
+ )
27
+ if installed.returncode == 0:
28
+ return
29
+ except FileNotFoundError:
30
+ pass
31
+
32
+ try:
33
+ os_platform = platform.system()
34
+ architecture = platform.machine()
35
+
36
+ if os_platform == "Linux":
37
+ if architecture == "aarch64" or architecture == "arm64":
38
+ arch = "arm64"
39
+ else:
40
+ arch = "amd64"
41
+ curr_os = "linux"
42
+ elif os_platform == "Darwin":
43
+ if architecture == "aarch64" or architecture == "arm64":
44
+ arch = "arm64"
45
+ else:
46
+ arch = "amd64"
47
+ curr_os = "darwin"
48
+ else:
49
+ raise OSError(f"Copa installation is not supported on {os_platform}")
50
+
51
+ url = f"https://github.com/project-copacetic/copacetic/releases/download/v{version}/copa_{version}_{curr_os}_{arch}.tar.gz"
52
+
53
+ response = requests.get(url, allow_redirects=True)
54
+ response.raise_for_status()
55
+
56
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.tar.gz') as temp_file:
57
+ temp_file.write(response.content)
58
+ temp_path = temp_file.name
59
+
60
+ with tarfile.open(temp_path, 'r:gz') as tar:
61
+ copa_member = None
62
+ for member in tar.getmembers():
63
+ if member.name.endswith('copa') and member.isfile():
64
+ copa_member = member
65
+ break
66
+
67
+ if copa_member:
68
+ tar.extract(copa_member, path=path)
69
+ extracted_path = os.path.join(path, copa_member.name)
70
+ if os.path.exists(extracted_path):
71
+ os.chmod(extracted_path, 0o755)
72
+
73
+ os.unlink(temp_path)
74
+ return extracted_path
75
+
76
+ except requests.RequestException as error:
77
+ logger.error(f"Error downloading Copa for {os_platform}: {error}")
78
+ except tarfile.TarError as error:
79
+ logger.error(f"Error extracting Copa archive: {error}")
80
+ except Exception as error:
81
+ logger.error(f"Error during Copa installation on {os_platform}: {error}")
82
+
83
+ def patch_image(
84
+ self,
85
+ image: str,
86
+ vulnerability_report: str,
87
+ output_image: Optional[str] = None,
88
+ patch_format: str = "trivy",
89
+ config: Optional[Dict] = None,
90
+ work_folder: str = ".",
91
+ platform: str = ""
92
+ ):
93
+ try:
94
+ config = config or {}
95
+ buildkit_config = config.get("BUILDKIT_CONFIG", {})
96
+ prefix = self.install_tool(config.get("VERSION"), path=work_folder)
97
+ output_file = f"{image.replace('/', '_')}-patch-vex.json"
98
+
99
+ copa_cmd = [
100
+ prefix,
101
+ "patch",
102
+ "--image",
103
+ image,
104
+ "--scanner",
105
+ patch_format,
106
+ "--format",
107
+ "openvex",
108
+ "--output",
109
+ output_file
110
+ ]
111
+
112
+ if vulnerability_report:
113
+ copa_cmd.extend(["--report", vulnerability_report])
114
+
115
+ buildkit_addr = buildkit_config.get("DEFAULT_ADDR")
116
+ if buildkit_addr:
117
+ copa_cmd.extend(["--addr", buildkit_addr])
118
+
119
+ progress_mode = buildkit_config.get("PROGRESS", "auto")
120
+ if progress_mode not in ["auto", "plain", "tty", "quiet", "rawjson"]:
121
+ raise ValueError(f"Invalid progress mode: {progress_mode}")
122
+ else:
123
+ copa_cmd.extend(["--progress", progress_mode])
124
+
125
+ if output_image:
126
+ copa_cmd.extend(["--tag", output_image])
127
+ else:
128
+ tag_suffix = config.get("DEFAULT_OUTPUT_SUFFIX", "-patched")
129
+ if tag_suffix.startswith("-"):
130
+ tag_suffix = tag_suffix[1:]
131
+ copa_cmd.extend(["--tag-suffix", tag_suffix])
132
+
133
+ if platform:
134
+ copa_cmd.extend(["--platform", platform])
135
+ elif not vulnerability_report:
136
+ raise ValueError(
137
+ "If a vulnerability report is not provided, the platforms to be patched must be provided."
138
+ )
139
+
140
+ timeout_duration = config.get("TIMEOUT", 5)
141
+ copa_cmd.extend(["--timeout", f"{timeout_duration}m"])
142
+
143
+ if buildkit_config.get("IGNORE_ERRORS", False):
144
+ copa_cmd.append("--ignore-errors")
145
+
146
+ result = subprocess.run(
147
+ copa_cmd,
148
+ capture_output=True,
149
+ text=True
150
+ )
151
+
152
+ if result.returncode == 0:
153
+ if os.path.exists(output_file):
154
+ subprocess.run(["chmod", "644", f"./{output_file}"])
155
+ patch_details = self._parse_copa_output(output_file)
156
+ else:
157
+ if not vulnerability_report:
158
+ logger.info("Note: VEX report file not generated (no vulnerability report provided)")
159
+ logger.info("Image patching completed successfully without VEX output")
160
+ patch_details = {
161
+ "vulnerabilities_patched": 0,
162
+ "details": [],
163
+ "packages_updated": [],
164
+ "platforms_processed": []
165
+ }
166
+
167
+ if output_image:
168
+ patched_image = output_image
169
+ else:
170
+ tag_suffix = config.get("DEFAULT_OUTPUT_SUFFIX", "-patched")
171
+ if ":" in image:
172
+ base_image, tag = image.rsplit(":", 1)
173
+ patched_image = f"{base_image}:{tag}{tag_suffix}"
174
+ else:
175
+ patched_image = f"{image}{tag_suffix}"
176
+
177
+ return {
178
+ "success": True,
179
+ "original_image": image,
180
+ "patched_image": patched_image,
181
+ "platforms_processed": patch_details.get("platforms_processed", []),
182
+ "vulnerabilities_patched": patch_details.get("vulnerabilities_patched", 0),
183
+ "packages_updated": len(patch_details.get("packages_updated", [])),
184
+ "patch_details": patch_details.get("details", []),
185
+ "copa_output": result.stdout,
186
+ "vex_file_generated": os.path.exists(output_file)
187
+ }
188
+ else:
189
+ error_msg = f"Copa command failed with return code {result.returncode}. Error: {result.stderr}"
190
+ logger.error(error_msg)
191
+ return {
192
+ "success": False,
193
+ "error": error_msg,
194
+ "copa_output": result.stdout,
195
+ "copa_error": result.stderr
196
+ }
197
+
198
+ except subprocess.TimeoutExpired:
199
+ error_msg = f"Copa command timed out after {timeout_duration} minutes"
200
+ logger.error(error_msg)
201
+ return {
202
+ "success": False,
203
+ "error": error_msg
204
+ }
205
+ except Exception as e:
206
+ error_msg = f"Unexpected error during Copa execution: {str(e)}"
207
+ logger.error(error_msg)
208
+ return {
209
+ "success": False,
210
+ "error": error_msg
211
+ }
212
+
213
+ def get_image_info(self, image: str) -> Dict:
214
+ try:
215
+ client = docker.from_env()
216
+ image_data = client.api.inspect_image(image)
217
+ rootfs = image_data.get("RootFS", {})
218
+
219
+ return {
220
+ "exists": True,
221
+ "architecture": image_data.get("Architecture"),
222
+ "os": image_data.get("Os"),
223
+ "size": image_data.get("Size"),
224
+ "layers": len(rootfs.get("Layers", []))
225
+ }
226
+
227
+ except Exception as e:
228
+ logger.error(f"Unexpected error while getting image info for '{image}': {str(e)}")
229
+ return {"exists": False, "error": str(e)}
230
+
231
+ def _parse_copa_output(self, output_path: str) -> Dict:
232
+ try:
233
+ if not os.path.exists(output_path):
234
+ logger.info(f"VEX output file not found at {output_path}")
235
+ return {
236
+ "vulnerabilities_patched": 0,
237
+ "details": [],
238
+ "packages_updated": [],
239
+ "platforms_processed": []
240
+ }
241
+
242
+ details = {
243
+ "vulnerabilities_patched": 0,
244
+ "details": [],
245
+ "packages_updated": set(),
246
+ "platforms_processed": set()
247
+ }
248
+
249
+ with open(output_path, "r", encoding="utf-8") as f:
250
+ data = json.load(f)
251
+
252
+ arch_str = "?arch="
253
+ for stmt in data.get("statements", []):
254
+ vuln_id = stmt.get("vulnerability", {}).get("@id")
255
+ status = stmt.get("status")
256
+ products = stmt.get("products", [])
257
+ packages = []
258
+
259
+ if status == "fixed":
260
+ details["vulnerabilities_patched"] += 1
261
+
262
+ for product in products:
263
+ for sub in product.get("subcomponents", []):
264
+ pkg_id = sub.get("@id", "")
265
+ packages.append(pkg_id.split(arch_str)[0])
266
+ details["packages_updated"].add(pkg_id)
267
+
268
+ if arch_str in pkg_id:
269
+ arch = pkg_id.split(arch_str)[-1]
270
+ details["platforms_processed"].add(arch)
271
+
272
+ details["details"].append({
273
+ "vulnerability": vuln_id,
274
+ "status": status,
275
+ "products": [p.get("@id") for p in products],
276
+ "packages": packages
277
+ })
278
+
279
+ details["packages_updated"] = list(details["packages_updated"])
280
+ details["platforms_processed"] = list(details["platforms_processed"])
281
+
282
+ return details
283
+
284
+ except FileNotFoundError:
285
+ logger.error(f"VEX output file not found: {output_path}")
286
+ return {
287
+ "vulnerabilities_patched": 0,
288
+ "details": [],
289
+ "packages_updated": [],
290
+ "platforms_processed": []
291
+ }
292
+ except json.JSONDecodeError as e:
293
+ logger.error(f"Error parsing VEX JSON file: {e}")
294
+ return {
295
+ "vulnerabilities_patched": 0,
296
+ "details": [],
297
+ "packages_updated": [],
298
+ "platforms_processed": []
299
+ }
300
+ except Exception as e:
301
+ logger.error(f"Error processing VEX output file {output_path}: {e}")
302
+ return {
303
+ "vulnerabilities_patched": 0,
304
+ "details": [],
305
+ "packages_updated": [],
306
+ "platforms_processed": []
307
+ }
308
+
309
+
@@ -0,0 +1,78 @@
1
+ from devsecops_engine_tools.engine_utilities.copacetic.src.domain.usecases.copacetic import (
2
+ Copacetic,
3
+ )
4
+ from devsecops_engine_tools.engine_core.src.domain.usecases.metrics_manager import (
5
+ MetricsManager,
6
+ )
7
+ import re
8
+ from devsecops_engine_tools.engine_utilities.utils.logger_info import MyLogger
9
+ from devsecops_engine_tools.engine_utilities import settings
10
+
11
+ logger = MyLogger.__call__(**settings.SETTING_LOGGER).get_logger()
12
+
13
+
14
+ def init_copacetic(
15
+ vulnerability_management_gateway,
16
+ secrets_manager_gateway,
17
+ devops_platform_gateway,
18
+ remote_config_source_gateway,
19
+ copacetic_gateway,
20
+ metrics_manager_gateway,
21
+ args,
22
+ ):
23
+ config_tool = remote_config_source_gateway.get_remote_config(
24
+ args["remote_config_repo"], "/engine_core/ConfigTool.json", args["remote_config_branch"]
25
+ )
26
+ copacetic_config_tool = remote_config_source_gateway.get_remote_config(
27
+ args["remote_config_repo"], "/engine_integrations/copacetic/ConfigTool.json", args["remote_config_branch"]
28
+ )
29
+ excluded_pipelines = remote_config_source_gateway.get_remote_config(
30
+ args["remote_config_repo"], "/engine_integrations/copacetic/Exclusions.json", args["remote_config_branch"]
31
+ )
32
+
33
+ pipeline_name = devops_platform_gateway.get_variable("pipeline_name")
34
+ branch = devops_platform_gateway.get_variable("branch_tag")
35
+
36
+ is_valid_pipeline = pipeline_name not in excluded_pipelines and not any(
37
+ [
38
+ re.match(
39
+ pattern,
40
+ pipeline_name,
41
+ re.IGNORECASE
42
+ ) for pattern in
43
+ [copacetic_config_tool["IGNORE_SEARCH_PATTERN"]] +
44
+ list(excluded_pipelines.get("BY_PATTERN_SEARCH", {}).keys())
45
+ ]
46
+ )
47
+
48
+ is_valid_branch = any(
49
+ target_branch in str(branch).split("/")
50
+ for target_branch in copacetic_config_tool["TARGET_BRANCHES"]
51
+ )
52
+
53
+ is_enabled = config_tool["COPACETIC"]["ENABLED"]
54
+
55
+ if is_enabled and is_valid_pipeline and is_valid_branch:
56
+ input_core = Copacetic(
57
+ vulnerability_management_gateway,
58
+ secrets_manager_gateway,
59
+ devops_platform_gateway,
60
+ remote_config_source_gateway,
61
+ copacetic_gateway,
62
+ ).process(args)
63
+
64
+ if args["send_metrics"] == "true":
65
+ MetricsManager(devops_platform_gateway, metrics_manager_gateway).process(
66
+ config_tool, input_core, {"module": "copacetic"}, ""
67
+ )
68
+ else:
69
+ if not is_enabled:
70
+ message = "DevSecOps Engine Tool - {0} in maintenance...".format(
71
+ "copacetic"
72
+ )
73
+ else:
74
+ message = "Tool skipped by DevSecOps policy"
75
+
76
+ print(
77
+ devops_platform_gateway.message("warning", message),
78
+ )
@@ -21,7 +21,7 @@ class TrivyDeserializator(DeseralizatorGateway):
21
21
  with open(image_scanned, "rb") as file:
22
22
  image_object = file.read()
23
23
  json_data = json.loads(image_object)
24
- vulnerabilities_data = json_data["Results"][0].get("Vulnerabilities", [])
24
+ vulnerabilities_data = json_data.get("Results", [{}])[0].get("Vulnerabilities", [])
25
25
  vulnerabilities = [
26
26
  Finding(
27
27
  id=vul.get("VulnerabilityID", ""),
@@ -67,7 +67,7 @@ class MyLogger(metaclass=SingletonType):
67
67
  if kwargs["debug"]:
68
68
  self._logger.setLevel(logging.DEBUG)
69
69
  else:
70
- self._logger.setLevel(logging.WARNING)
70
+ self._logger.setLevel(logging.INFO)
71
71
 
72
72
  if kwargs["log_file"]:
73
73
  now = datetime.datetime.now()
@@ -1 +1 @@
1
- version = '1.98.1'
1
+ version = '1.99.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devsecops-engine-tools
3
- Version: 1.98.1
3
+ Version: 1.99.0
4
4
  Summary: Tool for DevSecOps strategy
5
5
  Home-page: https://github.com/bancolombia/devsecops-engine-tools
6
6
  Author: Bancolombia DevSecOps Team
@@ -1,5 +1,5 @@
1
1
  devsecops_engine_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- devsecops_engine_tools/version.py,sha256=6XKqPw1Qns-9p7bTYKE6ONuM5GPexM87frVDf4c1dB8,19
2
+ devsecops_engine_tools/version.py,sha256=tMBfSsuNeyHzopCeNYga0hWZERf7c6oXvdOVAvTzRVE,19
3
3
  devsecops_engine_tools/engine_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  devsecops_engine_tools/engine_core/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  devsecops_engine_tools/engine_core/src/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -94,10 +94,10 @@ devsecops_engine_tools/engine_dast/src/infrastructure/helpers/json_handler.py,sh
94
94
  devsecops_engine_tools/engine_integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
95
  devsecops_engine_tools/engine_integrations/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
96
  devsecops_engine_tools/engine_integrations/src/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
- devsecops_engine_tools/engine_integrations/src/applications/runner_engine_integrations.py,sha256=oynNsETemfPXG7bKTaQnH161-OkVySt_6brB2Oqh8m8,5594
97
+ devsecops_engine_tools/engine_integrations/src/applications/runner_engine_integrations.py,sha256=EhOmi4L6X6oNlQtko860MG7eEweWyoV2aOLVMgbJGvY,7588
98
98
  devsecops_engine_tools/engine_integrations/src/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
99
  devsecops_engine_tools/engine_integrations/src/domain/usecases/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
- devsecops_engine_tools/engine_integrations/src/domain/usecases/handle_integrations.py,sha256=lPGwUNN9Oth3uQp-akNSUkJWnkP_o03lvkv4MnCsnO8,1935
100
+ devsecops_engine_tools/engine_integrations/src/domain/usecases/handle_integrations.py,sha256=SW-PF6u5r4Ffd5tjBt3qT9u5-lc-JfMpnyqyZGcWu30,2553
101
101
  devsecops_engine_tools/engine_integrations/src/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
102
  devsecops_engine_tools/engine_integrations/src/infrastructure/entry_points/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
103
  devsecops_engine_tools/engine_integrations/src/infrastructure/entry_points/entry_point_integrations.py,sha256=RV2oynZVww4q9KeH8ISCpVTT7ZwbNy6F99MwIGk_B5A,1194
@@ -226,7 +226,7 @@ devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_ada
226
226
  devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_adapters/prisma_cloud/prisma_cloud_manager_scan.py,sha256=Un0YmZeGh3LpOHiq6872lphD15cf02R9hwBUiHVuhCM,7848
227
227
  devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_adapters/prisma_cloud/prisma_deserialize_output.py,sha256=FXb0jUReJVUdZq_H_Zz-gCueMmWf0AwMiwJB-Ceqv2A,2695
228
228
  devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_adapters/trivy_tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
- devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_adapters/trivy_tool/trivy_manager_scan.py,sha256=WiVqnlLHRt5Ab5xIxdLCQRfas_HW8-j4tDkZKo_GdPM,3241
229
+ devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/driven_adapters/trivy_tool/trivy_manager_scan.py,sha256=Pg0Q0CKEQ-mE8u47H7ts1q3l0JOmTSeU9SBqr9_5U-Y,3839
230
230
  devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/entry_points/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
231
231
  devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/entry_points/entry_point_tool.py,sha256=MCBVnUxfjnax2stjn9ByM0Hy9LQ9vAMK9GZkOk3ex9M,3077
232
232
  devsecops_engine_tools/engine_sca/engine_container/src/infrastructure/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -268,6 +268,19 @@ devsecops_engine_tools/engine_utilities/azuredevops/infrastructure/azure_devops_
268
268
  devsecops_engine_tools/engine_utilities/azuredevops/models/AzureMessageLoggingPipeline.py,sha256=pCwlPDDl-hgvZ9gvceuC8GsKbsMhRm3ykhFFVByVqcI,664
269
269
  devsecops_engine_tools/engine_utilities/azuredevops/models/AzurePredefinedVariables.py,sha256=z9AGtc64o-BNTngiowRJFBlXmo_JmWqenL8YxdLs0aE,2561
270
270
  devsecops_engine_tools/engine_utilities/azuredevops/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
271
+ devsecops_engine_tools/engine_utilities/copacetic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
272
+ devsecops_engine_tools/engine_utilities/copacetic/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
273
+ devsecops_engine_tools/engine_utilities/copacetic/src/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
274
+ devsecops_engine_tools/engine_utilities/copacetic/src/applications/runner_copacetic.py,sha256=T4S3LMrVqA4RhpNaFezyILMLFkfNqFVm2Lz9h2o7IvQ,1488
275
+ devsecops_engine_tools/engine_utilities/copacetic/src/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
276
+ devsecops_engine_tools/engine_utilities/copacetic/src/domain/usecases/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
277
+ devsecops_engine_tools/engine_utilities/copacetic/src/domain/usecases/copacetic.py,sha256=IH9wRBLhmtjM5Cf_arRB_a3woTClDSNatI7FAsZOG6k,8381
278
+ devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
279
+ devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/driven_adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
280
+ devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/driven_adapters/copacetic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
281
+ devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/driven_adapters/copacetic/copacetic_adapter.py,sha256=Uc4zKMyMVnEGsQD5rMvPY1DzAtdmHNx_6b-4E7QDgWM,11957
282
+ devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/entry_points/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
283
+ devsecops_engine_tools/engine_utilities/copacetic/src/infrastructure/entry_points/entry_point_copacetic.py,sha256=3FU0rsOaGJq_T8Vlo-hZNk1P37xeoG4T3OFeIgiLKKs,2782
271
284
  devsecops_engine_tools/engine_utilities/defect_dojo/__init__.py,sha256=P-HiaN1sgDUekalZPCzSTF-zuqyXpNG2uVEsMDaC0ro,462
272
285
  devsecops_engine_tools/engine_utilities/defect_dojo/applications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
273
286
  devsecops_engine_tools/engine_utilities/defect_dojo/applications/component.py,sha256=Y6vA1nRfMCoqJEceRBDQ_QLpIKASqB-t8V1yqao-eUQ,1175
@@ -356,19 +369,19 @@ devsecops_engine_tools/engine_utilities/ssh/managment_private_key.py,sha256=Vvrr
356
369
  devsecops_engine_tools/engine_utilities/trivy_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
357
370
  devsecops_engine_tools/engine_utilities/trivy_utils/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
358
371
  devsecops_engine_tools/engine_utilities/trivy_utils/infrastructure/driven_adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
359
- devsecops_engine_tools/engine_utilities/trivy_utils/infrastructure/driven_adapters/trivy_deserialize_output.py,sha256=a9FM3n_oE9A_6PS6EU6dttBdKQDmoSMDAv3mcpxEpoE,5311
372
+ devsecops_engine_tools/engine_utilities/trivy_utils/infrastructure/driven_adapters/trivy_deserialize_output.py,sha256=7jiZ3FRKEnWw542ei6g4ZnGkpX8RInWND_dGwJVgbrs,5321
360
373
  devsecops_engine_tools/engine_utilities/trivy_utils/infrastructure/driven_adapters/trivy_manager_scan_utils.py,sha256=9bUT0V-EFhdik8aNuGTI2i4OnT1YvFT7s7xu5M5sejM,2888
361
374
  devsecops_engine_tools/engine_utilities/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
362
375
  devsecops_engine_tools/engine_utilities/utils/api_error.py,sha256=yRbad5gNUHh5nALBKkRDi-d98JPmqAhw-QJEGW4psrw,528
363
376
  devsecops_engine_tools/engine_utilities/utils/dataclass_classmethod.py,sha256=S-w6pybVKlyVBhV3HE3IGDvO4ByXxiVePP1JaMnISgM,4302
364
377
  devsecops_engine_tools/engine_utilities/utils/datetime_parsing.py,sha256=2891pkh01dfW8E5CW2eTpsUF1t6k0rgbQf8BgkdrSEk,224
365
- devsecops_engine_tools/engine_utilities/utils/logger_info.py,sha256=4Mz8Bwlm9MkufkBR6JVZWFum6-nrUYUKj6gfq-eFWgg,3575
378
+ devsecops_engine_tools/engine_utilities/utils/logger_info.py,sha256=rxNjzT0o64qfnmI5XyFXZRTz9C021o2QZim8nDQ1aKw,3572
366
379
  devsecops_engine_tools/engine_utilities/utils/name_conversion.py,sha256=ADJrRGaxYSDe0ZRh6VHRf53H4sXPcb-vNP_i81PUn3I,307
367
380
  devsecops_engine_tools/engine_utilities/utils/printers.py,sha256=amYAr9YQfYgR6jK9a2l26z3oovFPQ3FAKmhq6BKhEBA,623
368
381
  devsecops_engine_tools/engine_utilities/utils/session_manager.py,sha256=Z0fdhB3r-dxU0nGSD9zW_B4r2Qol1rUnUCkhFR0U-HQ,487
369
382
  devsecops_engine_tools/engine_utilities/utils/utils.py,sha256=HCjS900TBoNcHrC4LaiP-Kf9frVdtagF130qOUgnO2M,6757
370
- devsecops_engine_tools-1.98.1.dist-info/METADATA,sha256=_l-rVJll6Vp4oEAF8_OQVtknfNlMJJnXGqp9lirrPXA,3200
371
- devsecops_engine_tools-1.98.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
372
- devsecops_engine_tools-1.98.1.dist-info/entry_points.txt,sha256=OWAww5aBsGeMv0kWhSgVNB0ySKKpYuJd4dly0ikFPkc,283
373
- devsecops_engine_tools-1.98.1.dist-info/top_level.txt,sha256=ge6y0X_xBAU1aG3EMWFtl9djbVyg5BxuSp2r2Lg6EQU,23
374
- devsecops_engine_tools-1.98.1.dist-info/RECORD,,
383
+ devsecops_engine_tools-1.99.0.dist-info/METADATA,sha256=6_wQl9HE6GvHfuDEOSJNGy_B1n89UBF-uVIdc0F7E74,3200
384
+ devsecops_engine_tools-1.99.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
385
+ devsecops_engine_tools-1.99.0.dist-info/entry_points.txt,sha256=OWAww5aBsGeMv0kWhSgVNB0ySKKpYuJd4dly0ikFPkc,283
386
+ devsecops_engine_tools-1.99.0.dist-info/top_level.txt,sha256=ge6y0X_xBAU1aG3EMWFtl9djbVyg5BxuSp2r2Lg6EQU,23
387
+ devsecops_engine_tools-1.99.0.dist-info/RECORD,,