cycode 1.9.6.dev2__tar.gz → 1.9.6.dev4__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.
Files changed (111) hide show
  1. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/PKG-INFO +1 -1
  2. cycode-1.9.6.dev4/cycode/__init__.py +1 -0
  3. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/code_scanner.py +35 -13
  4. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/pre_commit/pre_commit_command.py +2 -2
  5. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/exceptions/handle_scan_errors.py +8 -5
  6. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/repository_documents.py +7 -7
  7. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/sca/sca_code_scanner.py +9 -7
  8. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/console_printer.py +4 -2
  9. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/json_printer.py +4 -2
  10. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/printer_base.py +1 -1
  11. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/tables/sca_table_printer.py +2 -3
  12. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/tables/table_printer.py +1 -1
  13. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/tables/table_printer_base.py +8 -2
  14. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/text_printer.py +19 -13
  15. cycode-1.9.6.dev4/cycode/cli/utils/git_proxy.py +76 -0
  16. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/scan_client.py +13 -1
  17. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/pyproject.toml +1 -1
  18. cycode-1.9.6.dev2/cycode/__init__.py +0 -1
  19. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/README.md +0 -0
  20. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/__init__.py +0 -0
  21. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/__init__.py +0 -0
  22. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/auth/__init__.py +0 -0
  23. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/auth/auth_command.py +0 -0
  24. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/auth/auth_manager.py +0 -0
  25. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/configure/__init__.py +0 -0
  26. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/configure/configure_command.py +0 -0
  27. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/ignore/__init__.py +0 -0
  28. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/ignore/ignore_command.py +0 -0
  29. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/main_cli.py +0 -0
  30. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/__init__.py +0 -0
  31. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/report_command.py +0 -0
  32. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/__init__.py +0 -0
  33. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/common.py +0 -0
  34. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/path/__init__.py +0 -0
  35. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/path/path_command.py +0 -0
  36. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/repository_url/__init__.py +0 -0
  37. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/repository_url/repository_url_command.py +0 -0
  38. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/sbom_command.py +0 -0
  39. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/report/sbom/sbom_report_file.py +0 -0
  40. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/__init__.py +0 -0
  41. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/commit_history/__init__.py +0 -0
  42. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/commit_history/commit_history_command.py +0 -0
  43. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/path/__init__.py +0 -0
  44. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/path/path_command.py +0 -0
  45. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/pre_commit/__init__.py +0 -0
  46. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/pre_receive/__init__.py +0 -0
  47. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/pre_receive/pre_receive_command.py +0 -0
  48. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/repository/__init__.py +0 -0
  49. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/repository/repository_command.py +0 -0
  50. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/scan_ci/__init__.py +0 -0
  51. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/scan_ci/ci_integrations.py +0 -0
  52. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/scan_ci/scan_ci_command.py +0 -0
  53. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/scan/scan_command.py +0 -0
  54. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/version/__init__.py +0 -0
  55. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/commands/version/version_command.py +0 -0
  56. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/config.py +0 -0
  57. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/config.yaml +0 -0
  58. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/consts.py +0 -0
  59. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/exceptions/__init__.py +0 -0
  60. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  61. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  62. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/__init__.py +0 -0
  63. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/excluder.py +0 -0
  64. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/iac/__init__.py +0 -0
  65. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  66. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/models/__init__.py +0 -0
  67. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  68. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/path_documents.py +0 -0
  69. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/sca/__init__.py +0 -0
  70. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  71. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/sca/maven/base_restore_maven_dependencies.py +0 -0
  72. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  73. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  74. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/files_collector/zip_documents.py +0 -0
  75. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/main.py +0 -0
  76. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/models.py +0 -0
  77. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/__init__.py +0 -0
  78. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/tables/__init__.py +0 -0
  79. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/tables/table.py +0 -0
  80. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/printers/tables/table_models.py +0 -0
  81. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/user_settings/__init__.py +0 -0
  82. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/user_settings/base_file_manager.py +0 -0
  83. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/user_settings/config_file_manager.py +0 -0
  84. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/user_settings/configuration_manager.py +0 -0
  85. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/user_settings/credentials_manager.py +0 -0
  86. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/user_settings/jwt_creator.py +0 -0
  87. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/__init__.py +0 -0
  88. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/enum_utils.py +0 -0
  89. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/get_api_client.py +0 -0
  90. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/path_utils.py +0 -0
  91. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/progress_bar.py +0 -0
  92. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/scan_batch.py +0 -0
  93. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/scan_utils.py +0 -0
  94. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/shell_executor.py +0 -0
  95. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/string_utils.py +0 -0
  96. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/task_timer.py +0 -0
  97. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cli/utils/yaml_utils.py +0 -0
  98. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/__init__.py +0 -0
  99. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/auth_client.py +0 -0
  100. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/client_creator.py +0 -0
  101. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/config.py +0 -0
  102. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/config.yaml +0 -0
  103. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/config_dev.py +0 -0
  104. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/cycode_client.py +0 -0
  105. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/cycode_client_base.py +0 -0
  106. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  107. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/cycode_token_based_client.py +0 -0
  108. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/headers.py +0 -0
  109. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/models.py +0 -0
  110. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/report_client.py +0 -0
  111. {cycode-1.9.6.dev2 → cycode-1.9.6.dev4}/cycode/cyclient/scan_config_base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cycode
3
- Version: 1.9.6.dev2
3
+ Version: 1.9.6.dev4
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  Home-page: https://github.com/cycodehq/cycode-cli
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ __version__ = '1.9.6.dev4' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
7
7
  from uuid import UUID, uuid4
8
8
 
9
9
  import click
10
- from git import NULL_TREE, Repo
11
10
 
12
11
  from cycode.cli import consts
13
12
  from cycode.cli.config import configuration_manager
@@ -28,9 +27,8 @@ from cycode.cli.files_collector.zip_documents import zip_documents
28
27
  from cycode.cli.models import CliError, Document, DocumentDetections, LocalScanResult, Severity
29
28
  from cycode.cli.printers import ConsolePrinter
30
29
  from cycode.cli.utils import scan_utils
31
- from cycode.cli.utils.path_utils import (
32
- get_path_by_os,
33
- )
30
+ from cycode.cli.utils.git_proxy import git_proxy
31
+ from cycode.cli.utils.path_utils import get_path_by_os
34
32
  from cycode.cli.utils.progress_bar import ScanProgressBarSection
35
33
  from cycode.cli.utils.scan_batch import run_parallel_batched_scan
36
34
  from cycode.cli.utils.scan_utils import set_issue_detected
@@ -148,14 +146,14 @@ def _enrich_scan_result_with_data_from_detection_rules(
148
146
 
149
147
  def _get_scan_documents_thread_func(
150
148
  context: click.Context, is_git_diff: bool, is_commit_range: bool, scan_parameters: dict
151
- ) -> Callable[[List[Document]], Tuple[str, CliError, LocalScanResult]]:
149
+ ) -> Tuple[Callable[[List[Document]], Tuple[str, CliError, LocalScanResult]], str]:
152
150
  cycode_client = context.obj['client']
153
151
  scan_type = context.obj['scan_type']
154
152
  severity_threshold = context.obj['severity_threshold']
155
153
  sync_option = context.obj['sync']
156
154
  command_scan_type = context.info_name
157
-
158
- scan_parameters['aggregation_id'] = str(_generate_unique_id())
155
+ aggregation_id = str(_generate_unique_id())
156
+ scan_parameters['aggregation_id'] = aggregation_id
159
157
 
160
158
  def _scan_batch_thread_func(batch: List[Document]) -> Tuple[str, CliError, LocalScanResult]:
161
159
  local_scan_result = error = error_message = None
@@ -224,7 +222,7 @@ def _get_scan_documents_thread_func(
224
222
 
225
223
  return scan_id, error, local_scan_result
226
224
 
227
- return _scan_batch_thread_func
225
+ return _scan_batch_thread_func, aggregation_id
228
226
 
229
227
 
230
228
  def scan_commit_range(
@@ -244,7 +242,7 @@ def scan_commit_range(
244
242
  documents_to_scan = []
245
243
  commit_ids_to_scan = []
246
244
 
247
- repo = Repo(path)
245
+ repo = git_proxy.get_repo(path)
248
246
  total_commits_count = int(repo.git.rev_list('--count', commit_range))
249
247
  logger.debug('Calculating diffs for %s commits in the commit range %s', total_commits_count, commit_range)
250
248
 
@@ -261,7 +259,7 @@ def scan_commit_range(
261
259
 
262
260
  commit_id = commit.hexsha
263
261
  commit_ids_to_scan.append(commit_id)
264
- parent = commit.parents[0] if commit.parents else NULL_TREE
262
+ parent = commit.parents[0] if commit.parents else git_proxy.get_null_tree()
265
263
  diff = commit.diff(parent, create_patch=True, R=True)
266
264
  commit_documents_to_scan = []
267
265
  for blob in diff:
@@ -313,11 +311,16 @@ def scan_documents(
313
311
  )
314
312
  return
315
313
 
316
- scan_batch_thread_func = _get_scan_documents_thread_func(context, is_git_diff, is_commit_range, scan_parameters)
314
+ scan_batch_thread_func, aggregation_id = _get_scan_documents_thread_func(
315
+ context, is_git_diff, is_commit_range, scan_parameters
316
+ )
317
317
  errors, local_scan_results = run_parallel_batched_scan(
318
318
  scan_batch_thread_func, documents_to_scan, progress_bar=progress_bar
319
319
  )
320
-
320
+ aggregation_report_url = _try_get_aggregation_report_url_if_needed(
321
+ scan_parameters, context.obj['client'], context.obj['scan_type']
322
+ )
323
+ set_aggregation_report_url(context, aggregation_report_url)
321
324
  progress_bar.set_section_length(ScanProgressBarSection.GENERATE_REPORT, 1)
322
325
  progress_bar.update(ScanProgressBarSection.GENERATE_REPORT)
323
326
  progress_bar.stop()
@@ -326,6 +329,25 @@ def scan_documents(
326
329
  print_results(context, local_scan_results, errors)
327
330
 
328
331
 
332
+ def set_aggregation_report_url(context: click.Context, aggregation_report_url: Optional[str] = None) -> None:
333
+ context.obj['aggregation_report_url'] = aggregation_report_url
334
+
335
+
336
+ def _try_get_aggregation_report_url_if_needed(
337
+ scan_parameters: dict, cycode_client: 'ScanClient', scan_type: str
338
+ ) -> Optional[str]:
339
+ aggregation_id = scan_parameters.get('aggregation_id')
340
+ if not scan_parameters.get('report'):
341
+ return None
342
+ if aggregation_id is None:
343
+ return None
344
+ try:
345
+ report_url_response = cycode_client.get_scan_aggregation_report_url(aggregation_id, scan_type)
346
+ return report_url_response.report_url
347
+ except Exception as e:
348
+ logger.debug('Failed to get aggregation report url: %s', str(e))
349
+
350
+
329
351
  def scan_commit_range_documents(
330
352
  context: click.Context,
331
353
  from_documents_to_scan: List[Document],
@@ -664,7 +686,7 @@ def get_scan_parameters(context: click.Context, paths: Tuple[str]) -> dict:
664
686
 
665
687
  def try_get_git_remote_url(path: str) -> Optional[str]:
666
688
  try:
667
- remote_url = Repo(path).remotes[0].config_reader.get('url')
689
+ remote_url = git_proxy.get_repo(path).remotes[0].config_reader.get('url')
668
690
  logger.debug('Found Git remote URL, %s', {'remote_url': remote_url, 'path': path})
669
691
  return remote_url
670
692
  except Exception as e:
@@ -2,7 +2,6 @@ import os
2
2
  from typing import List
3
3
 
4
4
  import click
5
- from git import Repo
6
5
 
7
6
  from cycode.cli import consts
8
7
  from cycode.cli.commands.scan.code_scanner import scan_documents, scan_sca_pre_commit
@@ -12,6 +11,7 @@ from cycode.cli.files_collector.repository_documents import (
12
11
  get_diff_file_path,
13
12
  )
14
13
  from cycode.cli.models import Document
14
+ from cycode.cli.utils.git_proxy import git_proxy
15
15
  from cycode.cli.utils.path_utils import (
16
16
  get_path_by_os,
17
17
  )
@@ -31,7 +31,7 @@ def pre_commit_command(context: click.Context, ignored_args: List[str]) -> None:
31
31
  scan_sca_pre_commit(context)
32
32
  return
33
33
 
34
- diff_files = Repo(os.getcwd()).index.diff('HEAD', create_patch=True, R=True)
34
+ diff_files = git_proxy.get_repo(os.getcwd()).index.diff('HEAD', create_patch=True, R=True)
35
35
 
36
36
  progress_bar.set_section_length(ScanProgressBarSection.PREPARE_LOCAL_FILES, len(diff_files))
37
37
 
@@ -1,11 +1,11 @@
1
1
  from typing import Optional
2
2
 
3
3
  import click
4
- from git import InvalidGitRepositoryError
5
4
 
6
5
  from cycode.cli.exceptions import custom_exceptions
7
6
  from cycode.cli.models import CliError, CliErrors
8
7
  from cycode.cli.printers import ConsolePrinter
8
+ from cycode.cli.utils.git_proxy import git_proxy
9
9
 
10
10
 
11
11
  def handle_scan_exception(
@@ -13,7 +13,7 @@ def handle_scan_exception(
13
13
  ) -> Optional[CliError]:
14
14
  context.obj['did_fail'] = True
15
15
 
16
- ConsolePrinter(context).print_exception()
16
+ ConsolePrinter(context).print_exception(e)
17
17
 
18
18
  errors: CliErrors = {
19
19
  custom_exceptions.NetworkError: CliError(
@@ -49,7 +49,7 @@ def handle_scan_exception(
49
49
  'Please make sure that your file is well formed '
50
50
  'and execute the scan again',
51
51
  ),
52
- InvalidGitRepositoryError: CliError(
52
+ git_proxy.get_invalid_git_repository_error(): CliError(
53
53
  soft_fail=False,
54
54
  code='invalid_git_error',
55
55
  message='The path you supplied does not correlate to a git repository. '
@@ -69,10 +69,13 @@ def handle_scan_exception(
69
69
  ConsolePrinter(context).print_error(error)
70
70
  return None
71
71
 
72
+ unknown_error = CliError(code='unknown_error', message=str(e))
73
+
72
74
  if return_exception:
73
- return CliError(code='unknown_error', message=str(e))
75
+ return unknown_error
74
76
 
75
77
  if isinstance(e, click.ClickException):
76
78
  raise e
77
79
 
78
- raise click.ClickException(str(e))
80
+ ConsolePrinter(context).print_error(unknown_error)
81
+ exit(1)
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Iterator, List, Optional, Tuple, Union
4
4
  from cycode.cli import consts
5
5
  from cycode.cli.files_collector.sca import sca_code_scanner
6
6
  from cycode.cli.models import Document
7
+ from cycode.cli.utils.git_proxy import git_proxy
7
8
  from cycode.cli.utils.path_utils import get_file_content, get_path_by_os
8
9
 
9
10
  if TYPE_CHECKING:
@@ -13,8 +14,6 @@ if TYPE_CHECKING:
13
14
 
14
15
  from cycode.cli.utils.progress_bar import BaseProgressBar, ProgressBarSection
15
16
 
16
- from git import Repo
17
-
18
17
 
19
18
  def should_process_git_object(obj: 'Blob', _: int) -> bool:
20
19
  return obj.type == 'blob' and obj.size > 0
@@ -23,14 +22,14 @@ def should_process_git_object(obj: 'Blob', _: int) -> bool:
23
22
  def get_git_repository_tree_file_entries(
24
23
  path: str, branch: str
25
24
  ) -> Union[Iterator['IndexObjUnion'], Iterator['TraversedTreeTup']]:
26
- return Repo(path).tree(branch).traverse(predicate=should_process_git_object)
25
+ return git_proxy.get_repo(path).tree(branch).traverse(predicate=should_process_git_object)
27
26
 
28
27
 
29
28
  def parse_commit_range(commit_range: str, path: str) -> Tuple[str, str]:
30
29
  from_commit_rev = None
31
30
  to_commit_rev = None
32
31
 
33
- for commit in Repo(path).iter_commits(rev=commit_range):
32
+ for commit in git_proxy.get_repo(path).iter_commits(rev=commit_range):
34
33
  if not to_commit_rev:
35
34
  to_commit_rev = commit.hexsha
36
35
  from_commit_rev = commit.hexsha
@@ -52,7 +51,7 @@ def get_pre_commit_modified_documents(
52
51
  git_head_documents = []
53
52
  pre_committed_documents = []
54
53
 
55
- repo = Repo(os.getcwd())
54
+ repo = git_proxy.get_repo(os.getcwd())
56
55
  diff_files = repo.index.diff(consts.GIT_HEAD_COMMIT_REV, create_patch=True, R=True)
57
56
  progress_bar.set_section_length(progress_bar_section, len(diff_files))
58
57
  for file in diff_files:
@@ -82,7 +81,7 @@ def get_commit_range_modified_documents(
82
81
  from_commit_documents = []
83
82
  to_commit_documents = []
84
83
 
85
- repo = Repo(path)
84
+ repo = git_proxy.get_repo(path)
86
85
  diff = repo.commit(from_commit_rev).diff(to_commit_rev)
87
86
 
88
87
  modified_files_diff = [
@@ -131,7 +130,8 @@ def _get_end_commit_from_branch_update_details(update_details: str) -> str:
131
130
  def _get_oldest_unupdated_commit_for_branch(commit: str) -> Optional[str]:
132
131
  # get a list of commits by chronological order that are not in the remote repository yet
133
132
  # more info about rev-list command: https://git-scm.com/docs/git-rev-list
134
- not_updated_commits = Repo(os.getcwd()).git.rev_list(commit, '--topo-order', '--reverse', '--not', '--all')
133
+ repo = git_proxy.get_repo(os.getcwd())
134
+ not_updated_commits = repo.git.rev_list(commit, '--topo-order', '--reverse', '--not', '--all')
135
135
 
136
136
  commits = not_updated_commits.splitlines()
137
137
  if not commits:
@@ -2,16 +2,18 @@ import os
2
2
  from typing import TYPE_CHECKING, Dict, List, Optional
3
3
 
4
4
  import click
5
- from git import GitCommandError, Repo
6
5
 
7
6
  from cycode.cli import consts
8
7
  from cycode.cli.files_collector.sca.maven.restore_gradle_dependencies import RestoreGradleDependencies
9
8
  from cycode.cli.files_collector.sca.maven.restore_maven_dependencies import RestoreMavenDependencies
10
9
  from cycode.cli.models import Document
10
+ from cycode.cli.utils.git_proxy import git_proxy
11
11
  from cycode.cli.utils.path_utils import get_file_content, get_file_dir, join_paths
12
12
  from cycode.cyclient import logger
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from git import Repo
16
+
15
17
  from cycode.cli.files_collector.sca.maven.base_restore_maven_dependencies import BaseRestoreMavenDependencies
16
18
 
17
19
  BUILD_GRADLE_FILE_NAME = 'build.gradle'
@@ -27,7 +29,7 @@ def perform_pre_commit_range_scan_actions(
27
29
  to_commit_documents: List[Document],
28
30
  to_commit_rev: str,
29
31
  ) -> None:
30
- repo = Repo(path)
32
+ repo = git_proxy.get_repo(path)
31
33
  add_ecosystem_related_files_if_exists(from_commit_documents, repo, from_commit_rev)
32
34
  add_ecosystem_related_files_if_exists(to_commit_documents, repo, to_commit_rev)
33
35
 
@@ -35,13 +37,13 @@ def perform_pre_commit_range_scan_actions(
35
37
  def perform_pre_hook_range_scan_actions(
36
38
  git_head_documents: List[Document], pre_committed_documents: List[Document]
37
39
  ) -> None:
38
- repo = Repo(os.getcwd())
40
+ repo = git_proxy.get_repo(os.getcwd())
39
41
  add_ecosystem_related_files_if_exists(git_head_documents, repo, consts.GIT_HEAD_COMMIT_REV)
40
42
  add_ecosystem_related_files_if_exists(pre_committed_documents)
41
43
 
42
44
 
43
45
  def add_ecosystem_related_files_if_exists(
44
- documents: List[Document], repo: Optional[Repo] = None, commit_rev: Optional[str] = None
46
+ documents: List[Document], repo: Optional['Repo'] = None, commit_rev: Optional[str] = None
45
47
  ) -> None:
46
48
  documents_to_add: List[Document] = []
47
49
  for doc in documents:
@@ -56,7 +58,7 @@ def add_ecosystem_related_files_if_exists(
56
58
 
57
59
 
58
60
  def get_doc_ecosystem_related_project_files(
59
- doc: Document, documents: List[Document], ecosystem: str, commit_rev: Optional[str], repo: Optional[Repo]
61
+ doc: Document, documents: List[Document], ecosystem: str, commit_rev: Optional[str], repo: Optional['Repo']
60
62
  ) -> List[Document]:
61
63
  documents_to_add: List[Document] = []
62
64
  for ecosystem_project_file in consts.PROJECT_FILES_BY_ECOSYSTEM_MAP.get(ecosystem):
@@ -136,10 +138,10 @@ def get_manifest_file_path(document: Document, is_monitor_action: bool, project_
136
138
  return join_paths(project_path, document.path) if is_monitor_action else document.path
137
139
 
138
140
 
139
- def get_file_content_from_commit(repo: Repo, commit: str, file_path: str) -> Optional[str]:
141
+ def get_file_content_from_commit(repo: 'Repo', commit: str, file_path: str) -> Optional[str]:
140
142
  try:
141
143
  return repo.git.show(f'{commit}:{file_path}')
142
- except GitCommandError:
144
+ except git_proxy.get_git_command_error():
143
145
  return None
144
146
 
145
147
 
@@ -28,13 +28,15 @@ class ConsolePrinter:
28
28
  self.context = context
29
29
  self.scan_type = self.context.obj.get('scan_type')
30
30
  self.output_type = self.context.obj.get('output')
31
-
31
+ self.aggregation_report_url = self.context.obj.get('aggregation_report_url')
32
32
  self._printer_class = self._AVAILABLE_PRINTERS.get(self.output_type)
33
33
  if self._printer_class is None:
34
34
  raise CycodeError(f'"{self.output_type}" output type is not supported.')
35
35
 
36
36
  def print_scan_results(
37
- self, local_scan_results: List['LocalScanResult'], errors: Optional[Dict[str, 'CliError']] = None
37
+ self,
38
+ local_scan_results: List['LocalScanResult'],
39
+ errors: Optional[Dict[str, 'CliError']] = None,
38
40
  ) -> None:
39
41
  printer = self._get_scan_printer()
40
42
  printer.print_scan_results(local_scan_results, errors)
@@ -28,13 +28,15 @@ class JsonPrinter(PrinterBase):
28
28
  scan_ids = []
29
29
  report_urls = []
30
30
  detections = []
31
+ aggregation_report_url = self.context.obj.get('aggregation_report_url')
32
+ if aggregation_report_url:
33
+ report_urls.append(aggregation_report_url)
31
34
 
32
35
  for local_scan_result in local_scan_results:
33
36
  scan_ids.append(local_scan_result.scan_id)
34
37
 
35
- if local_scan_result.report_url:
38
+ if not aggregation_report_url and local_scan_result.report_url:
36
39
  report_urls.append(local_scan_result.report_url)
37
-
38
40
  for document_detections in local_scan_result.document_detections:
39
41
  detections.extend(document_detections.detections)
40
42
 
@@ -43,7 +43,7 @@ class PrinterBase(ABC):
43
43
  # gets the most recent exception caught by an except clause
44
44
  message = f'Error: {traceback.format_exc()}'
45
45
  else:
46
- traceback_message = ''.join(traceback.format_exception(e))
46
+ traceback_message = ''.join(traceback.format_exception(None, e, e.__traceback__))
47
47
  message = f'Error: {traceback_message}'
48
48
 
49
49
  click.secho(message, err=True, fg=self.RED_COLOR_NAME)
@@ -13,7 +13,6 @@ from cycode.cli.utils.string_utils import shortcut_dependency_paths
13
13
  if TYPE_CHECKING:
14
14
  from cycode.cli.models import LocalScanResult
15
15
 
16
-
17
16
  column_builder = ColumnInfoBuilder()
18
17
 
19
18
  # Building must have strict order. Represents the order of the columns in the table (from left to right)
@@ -29,7 +28,6 @@ LICENSE_COLUMN = column_builder.build(name='License')
29
28
  DIRECT_DEPENDENCY_COLUMN = column_builder.build(name='Direct Dependency')
30
29
  DEVELOPMENT_DEPENDENCY_COLUMN = column_builder.build(name='Development Dependency')
31
30
 
32
-
33
31
  COLUMN_WIDTHS_CONFIG: ColumnWidths = {
34
32
  REPOSITORY_COLUMN: 2,
35
33
  CODE_PROJECT_COLUMN: 2,
@@ -42,6 +40,7 @@ COLUMN_WIDTHS_CONFIG: ColumnWidths = {
42
40
 
43
41
  class ScaTablePrinter(TablePrinterBase):
44
42
  def _print_results(self, local_scan_results: List['LocalScanResult']) -> None:
43
+ aggregation_report_url = self.context.obj.get('aggregation_report_url')
45
44
  detections_per_policy_id = self._extract_detections_per_policy_id(local_scan_results)
46
45
  for policy_id, detections in detections_per_policy_id.items():
47
46
  table = self._get_table(policy_id)
@@ -53,7 +52,7 @@ class ScaTablePrinter(TablePrinterBase):
53
52
  self._print_summary_issues(len(detections), self._get_title(policy_id))
54
53
  self._print_table(table)
55
54
 
56
- self._print_report_urls(local_scan_results)
55
+ self._print_report_urls(local_scan_results, aggregation_report_url)
57
56
 
58
57
  @staticmethod
59
58
  def _get_title(policy_id: str) -> str:
@@ -63,7 +63,7 @@ class TablePrinter(TablePrinterBase):
63
63
  self._enrich_table_with_values(table, detection, document_detections.document)
64
64
 
65
65
  self._print_table(table)
66
- self._print_report_urls(local_scan_results)
66
+ self._print_report_urls(local_scan_results, self.context.obj.get('aggregation_report_url'))
67
67
 
68
68
  def _get_table(self) -> Table:
69
69
  table = Table()
@@ -58,9 +58,15 @@ class TablePrinterBase(PrinterBase, abc.ABC):
58
58
  click.echo(table.get_table().draw())
59
59
 
60
60
  @staticmethod
61
- def _print_report_urls(local_scan_results: List['LocalScanResult']) -> None:
61
+ def _print_report_urls(
62
+ local_scan_results: List['LocalScanResult'],
63
+ aggregation_report_url: Optional[str] = None,
64
+ ) -> None:
62
65
  report_urls = [scan_result.report_url for scan_result in local_scan_results if scan_result.report_url]
63
- if not report_urls:
66
+ if not report_urls and not aggregation_report_url:
67
+ return
68
+ if aggregation_report_url:
69
+ click.echo(f'Report URL: {aggregation_report_url}')
64
70
  return
65
71
 
66
72
  click.echo('Report URLs:')
@@ -39,10 +39,11 @@ class TextPrinter(PrinterBase):
39
39
 
40
40
  for local_scan_result in local_scan_results:
41
41
  for document_detections in local_scan_result.document_detections:
42
- self._print_document_detections(
43
- document_detections, local_scan_result.scan_id, local_scan_result.report_url
44
- )
42
+ self._print_document_detections(document_detections, local_scan_result.scan_id)
45
43
 
44
+ report_urls = [scan_result.report_url for scan_result in local_scan_results if scan_result.report_url]
45
+
46
+ self._print_report_urls(report_urls, self.context.obj.get('aggregation_report_url'))
46
47
  if not errors:
47
48
  return
48
49
 
@@ -55,18 +56,14 @@ class TextPrinter(PrinterBase):
55
56
  click.echo(f'- {scan_id}: ', nl=False)
56
57
  self.print_error(error)
57
58
 
58
- def _print_document_detections(
59
- self, document_detections: DocumentDetections, scan_id: str, report_url: Optional[str]
60
- ) -> None:
59
+ def _print_document_detections(self, document_detections: DocumentDetections, scan_id: str) -> None:
61
60
  document = document_detections.document
62
61
  lines_to_display = self._get_lines_to_display_count()
63
62
  for detection in document_detections.detections:
64
- self._print_detection_summary(detection, document.path, scan_id, report_url)
63
+ self._print_detection_summary(detection, document.path, scan_id)
65
64
  self._print_detection_code_segment(detection, document, lines_to_display)
66
65
 
67
- def _print_detection_summary(
68
- self, detection: Detection, document_path: str, scan_id: str, report_url: Optional[str]
69
- ) -> None:
66
+ def _print_detection_summary(self, detection: Detection, document_path: str, scan_id: str) -> None:
70
67
  detection_name = detection.type if self.scan_type == SECRET_SCAN_TYPE else detection.message
71
68
  detection_name_styled = click.style(detection_name, fg='bright_red', bold=True)
72
69
 
@@ -74,8 +71,6 @@ class TextPrinter(PrinterBase):
74
71
  detection_sha_message = f'\nSecret SHA: {detection_sha}' if detection_sha else ''
75
72
 
76
73
  scan_id_message = f'\nScan ID: {scan_id}'
77
- report_url_message = f'\nReport URL: {report_url}' if report_url else ''
78
-
79
74
  detection_commit_id = detection.detection_details.get('commit_id')
80
75
  detection_commit_id_message = f'\nCommit SHA: {detection_commit_id}' if detection_commit_id else ''
81
76
 
@@ -88,7 +83,6 @@ class TextPrinter(PrinterBase):
88
83
  f'(rule ID: {detection.detection_rule_id}) in file: {click.format_filename(document_path)} '
89
84
  f'{detection_sha_message}'
90
85
  f'{scan_id_message}'
91
- f'{report_url_message}'
92
86
  f'{detection_commit_id_message}'
93
87
  f'{company_guidelines_message}'
94
88
  f' ⛔'
@@ -101,6 +95,18 @@ class TextPrinter(PrinterBase):
101
95
 
102
96
  self._print_detection_from_file(detection, document, code_segment_size)
103
97
 
98
+ @staticmethod
99
+ def _print_report_urls(report_urls: List[str], aggregation_report_url: Optional[str] = None) -> None:
100
+ if not report_urls and not aggregation_report_url:
101
+ return
102
+ if aggregation_report_url:
103
+ click.echo(f'Report URL: {aggregation_report_url}')
104
+ return
105
+
106
+ click.echo('Report URLs:')
107
+ for report_url in report_urls:
108
+ click.echo(f'- {report_url}')
109
+
104
110
  @staticmethod
105
111
  def _get_code_segment_start_line(detection_line: int, code_segment_size: int) -> int:
106
112
  start_line = detection_line - math.ceil(code_segment_size / 2)
@@ -0,0 +1,76 @@
1
+ import types
2
+ from abc import ABC, abstractmethod
3
+ from typing import TYPE_CHECKING, Optional, Type
4
+
5
+ _GIT_ERROR_MESSAGE = """
6
+ Cycode CLI needs the git executable to be installed on the system.
7
+ Git executable must be available in the PATH.
8
+ Git 1.7.x or newer is required.
9
+ You can help Cycode CLI to locate the Git executable
10
+ by setting the GIT_PYTHON_GIT_EXECUTABLE=<path/to/git> environment variable.
11
+ """.strip().replace('\n', ' ')
12
+
13
+ try:
14
+ import git
15
+ except ImportError:
16
+ git = None
17
+
18
+ if TYPE_CHECKING:
19
+ from git import PathLike, Repo
20
+
21
+
22
+ class GitProxyError(Exception):
23
+ pass
24
+
25
+
26
+ class _AbstractGitProxy(ABC):
27
+ @abstractmethod
28
+ def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo':
29
+ ...
30
+
31
+ @abstractmethod
32
+ def get_null_tree(self) -> object:
33
+ ...
34
+
35
+ @abstractmethod
36
+ def get_invalid_git_repository_error(self) -> Type[BaseException]:
37
+ ...
38
+
39
+ @abstractmethod
40
+ def get_git_command_error(self) -> Type[BaseException]:
41
+ ...
42
+
43
+
44
+ class _DummyGitProxy(_AbstractGitProxy):
45
+ def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo':
46
+ raise RuntimeError(_GIT_ERROR_MESSAGE)
47
+
48
+ def get_null_tree(self) -> object:
49
+ raise RuntimeError(_GIT_ERROR_MESSAGE)
50
+
51
+ def get_invalid_git_repository_error(self) -> Type[BaseException]:
52
+ return GitProxyError
53
+
54
+ def get_git_command_error(self) -> Type[BaseException]:
55
+ return GitProxyError
56
+
57
+
58
+ class _GitProxy(_AbstractGitProxy):
59
+ def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo':
60
+ return git.Repo(path, *args, **kwargs)
61
+
62
+ def get_null_tree(self) -> object:
63
+ return git.NULL_TREE
64
+
65
+ def get_invalid_git_repository_error(self) -> Type[BaseException]:
66
+ return git.InvalidGitRepositoryError
67
+
68
+ def get_git_command_error(self) -> Type[BaseException]:
69
+ return git.GitCommandError
70
+
71
+
72
+ def get_git_proxy(git_module: Optional[types.ModuleType]) -> _AbstractGitProxy:
73
+ return _GitProxy() if git_module else _DummyGitProxy()
74
+
75
+
76
+ git_proxy = get_git_proxy(git)
@@ -59,7 +59,7 @@ class ScanClient:
59
59
  self, scan_type: str, should_use_scan_service: bool = False, should_use_sync_flow: bool = False
60
60
  ) -> str:
61
61
  service_path = self.scan_config.get_service_name(scan_type, should_use_scan_service)
62
- controller_path = self.get_scan_controller_path(scan_type)
62
+ controller_path = self.get_scan_controller_path(scan_type, should_use_scan_service)
63
63
  flow_type = self.get_scan_flow_type(should_use_sync_flow)
64
64
  return f'{service_path}/{controller_path}{flow_type}'
65
65
 
@@ -92,6 +92,12 @@ class ScanClient:
92
92
  response = self.scan_cycode_client.get(url_path=self.get_scan_report_url_path(scan_id, scan_type))
93
93
  return models.ScanReportUrlResponseSchema().build_dto(response.json())
94
94
 
95
+ def get_scan_aggregation_report_url(self, aggregation_id: str, scan_type: str) -> models.ScanReportUrlResponse:
96
+ response = self.scan_cycode_client.get(
97
+ url_path=self.get_scan_aggregation_report_url_path(aggregation_id, scan_type)
98
+ )
99
+ return models.ScanReportUrlResponseSchema().build_dto(response.json())
100
+
95
101
  def get_zipped_file_scan_async_url_path(self, scan_type: str, should_use_sync_flow: bool = False) -> str:
96
102
  async_scan_type = self.scan_config.get_async_scan_type(scan_type)
97
103
  async_entity_type = self.scan_config.get_async_entity_type(scan_type)
@@ -155,6 +161,12 @@ class ScanClient:
155
161
  def get_scan_report_url_path(self, scan_id: str, scan_type: str) -> str:
156
162
  return f'{self.get_scan_service_url_path(scan_type, should_use_scan_service=True)}/reportUrl/{scan_id}'
157
163
 
164
+ def get_scan_aggregation_report_url_path(self, aggregation_id: str, scan_type: str) -> str:
165
+ return (
166
+ f'{self.get_scan_service_url_path(scan_type, should_use_scan_service=True)}'
167
+ f'/reportUrlByAggregationId/{aggregation_id}'
168
+ )
169
+
158
170
  def get_scan_details(self, scan_type: str, scan_id: str) -> models.ScanDetailsResponse:
159
171
  path = self.get_scan_details_path(scan_type, scan_id)
160
172
  response = self.scan_cycode_client.get(url_path=path)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cycode"
3
- version = "1.9.6.dev2" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
3
+ version = "1.9.6.dev4" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
4
4
  description = "Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning."
5
5
  keywords=["secret-scan", "cycode", "devops", "token", "secret", "security", "cycode", "code"]
6
6
  authors = ["Cycode <support@cycode.com>"]
@@ -1 +0,0 @@
1
- __version__ = '1.9.6.dev2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
File without changes