regscale-cli 6.16.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.

Files changed (481) hide show
  1. regscale/__init__.py +1 -0
  2. regscale/airflow/__init__.py +9 -0
  3. regscale/airflow/azure/__init__.py +9 -0
  4. regscale/airflow/azure/cli.py +89 -0
  5. regscale/airflow/azure/upload_dags.py +116 -0
  6. regscale/airflow/click_dags.py +127 -0
  7. regscale/airflow/click_mixins.py +82 -0
  8. regscale/airflow/config.py +25 -0
  9. regscale/airflow/factories/__init__.py +0 -0
  10. regscale/airflow/factories/connections.py +58 -0
  11. regscale/airflow/factories/workflows.py +78 -0
  12. regscale/airflow/hierarchy.py +88 -0
  13. regscale/airflow/operators/__init__.py +0 -0
  14. regscale/airflow/operators/click.py +36 -0
  15. regscale/airflow/sensors/__init__.py +0 -0
  16. regscale/airflow/sensors/sql.py +107 -0
  17. regscale/airflow/sessions/__init__.py +0 -0
  18. regscale/airflow/sessions/sql/__init__.py +3 -0
  19. regscale/airflow/sessions/sql/queries.py +64 -0
  20. regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
  21. regscale/airflow/tasks/__init__.py +0 -0
  22. regscale/airflow/tasks/branches.py +22 -0
  23. regscale/airflow/tasks/cli.py +116 -0
  24. regscale/airflow/tasks/click.py +73 -0
  25. regscale/airflow/tasks/debugging.py +9 -0
  26. regscale/airflow/tasks/groups.py +116 -0
  27. regscale/airflow/tasks/init.py +60 -0
  28. regscale/airflow/tasks/states.py +47 -0
  29. regscale/airflow/tasks/workflows.py +36 -0
  30. regscale/ansible/__init__.py +9 -0
  31. regscale/core/__init__.py +0 -0
  32. regscale/core/app/__init__.py +3 -0
  33. regscale/core/app/api.py +571 -0
  34. regscale/core/app/application.py +665 -0
  35. regscale/core/app/internal/__init__.py +136 -0
  36. regscale/core/app/internal/admin_actions.py +230 -0
  37. regscale/core/app/internal/assessments_editor.py +873 -0
  38. regscale/core/app/internal/catalog.py +316 -0
  39. regscale/core/app/internal/comparison.py +459 -0
  40. regscale/core/app/internal/control_editor.py +571 -0
  41. regscale/core/app/internal/encrypt.py +79 -0
  42. regscale/core/app/internal/evidence.py +1240 -0
  43. regscale/core/app/internal/file_uploads.py +151 -0
  44. regscale/core/app/internal/healthcheck.py +66 -0
  45. regscale/core/app/internal/login.py +305 -0
  46. regscale/core/app/internal/migrations.py +240 -0
  47. regscale/core/app/internal/model_editor.py +1701 -0
  48. regscale/core/app/internal/poam_editor.py +632 -0
  49. regscale/core/app/internal/workflow.py +105 -0
  50. regscale/core/app/logz.py +74 -0
  51. regscale/core/app/utils/XMLIR.py +258 -0
  52. regscale/core/app/utils/__init__.py +0 -0
  53. regscale/core/app/utils/api_handler.py +358 -0
  54. regscale/core/app/utils/app_utils.py +1110 -0
  55. regscale/core/app/utils/catalog_utils/__init__.py +0 -0
  56. regscale/core/app/utils/catalog_utils/common.py +91 -0
  57. regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
  58. regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
  59. regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
  60. regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
  61. regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
  62. regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
  63. regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
  64. regscale/core/app/utils/file_utils.py +238 -0
  65. regscale/core/app/utils/parser_utils.py +81 -0
  66. regscale/core/app/utils/pickle_file_handler.py +57 -0
  67. regscale/core/app/utils/regscale_utils.py +319 -0
  68. regscale/core/app/utils/report_utils.py +119 -0
  69. regscale/core/app/utils/variables.py +226 -0
  70. regscale/core/decorators.py +31 -0
  71. regscale/core/lazy_group.py +65 -0
  72. regscale/core/login.py +63 -0
  73. regscale/core/server/__init__.py +0 -0
  74. regscale/core/server/flask_api.py +473 -0
  75. regscale/core/server/helpers.py +373 -0
  76. regscale/core/server/rest.py +64 -0
  77. regscale/core/server/static/css/bootstrap.css +6030 -0
  78. regscale/core/server/static/css/bootstrap.min.css +6 -0
  79. regscale/core/server/static/css/main.css +176 -0
  80. regscale/core/server/static/images/regscale-cli.svg +49 -0
  81. regscale/core/server/static/images/regscale.svg +38 -0
  82. regscale/core/server/templates/base.html +74 -0
  83. regscale/core/server/templates/index.html +43 -0
  84. regscale/core/server/templates/login.html +28 -0
  85. regscale/core/server/templates/make_base64.html +22 -0
  86. regscale/core/server/templates/upload_STIG.html +109 -0
  87. regscale/core/server/templates/upload_STIG_result.html +26 -0
  88. regscale/core/server/templates/upload_ssp.html +144 -0
  89. regscale/core/server/templates/upload_ssp_result.html +128 -0
  90. regscale/core/static/__init__.py +0 -0
  91. regscale/core/static/regex.py +14 -0
  92. regscale/core/utils/__init__.py +117 -0
  93. regscale/core/utils/click_utils.py +13 -0
  94. regscale/core/utils/date.py +238 -0
  95. regscale/core/utils/graphql.py +254 -0
  96. regscale/core/utils/urls.py +23 -0
  97. regscale/dev/__init__.py +6 -0
  98. regscale/dev/analysis.py +454 -0
  99. regscale/dev/cli.py +235 -0
  100. regscale/dev/code_gen.py +492 -0
  101. regscale/dev/dirs.py +69 -0
  102. regscale/dev/docs.py +384 -0
  103. regscale/dev/monitoring.py +26 -0
  104. regscale/dev/profiling.py +216 -0
  105. regscale/exceptions/__init__.py +4 -0
  106. regscale/exceptions/license_exception.py +7 -0
  107. regscale/exceptions/validation_exception.py +9 -0
  108. regscale/integrations/__init__.py +1 -0
  109. regscale/integrations/commercial/__init__.py +486 -0
  110. regscale/integrations/commercial/ad.py +433 -0
  111. regscale/integrations/commercial/amazon/__init__.py +0 -0
  112. regscale/integrations/commercial/amazon/common.py +106 -0
  113. regscale/integrations/commercial/aqua/__init__.py +0 -0
  114. regscale/integrations/commercial/aqua/aqua.py +91 -0
  115. regscale/integrations/commercial/aws/__init__.py +6 -0
  116. regscale/integrations/commercial/aws/cli.py +322 -0
  117. regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
  118. regscale/integrations/commercial/aws/inventory/base.py +64 -0
  119. regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
  120. regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
  121. regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
  122. regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
  123. regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
  124. regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
  125. regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
  126. regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
  127. regscale/integrations/commercial/aws/scanner.py +823 -0
  128. regscale/integrations/commercial/azure/__init__.py +0 -0
  129. regscale/integrations/commercial/azure/common.py +32 -0
  130. regscale/integrations/commercial/azure/intune.py +488 -0
  131. regscale/integrations/commercial/azure/scanner.py +49 -0
  132. regscale/integrations/commercial/burp.py +78 -0
  133. regscale/integrations/commercial/cpe.py +144 -0
  134. regscale/integrations/commercial/crowdstrike.py +1117 -0
  135. regscale/integrations/commercial/defender.py +1511 -0
  136. regscale/integrations/commercial/dependabot.py +210 -0
  137. regscale/integrations/commercial/durosuite/__init__.py +0 -0
  138. regscale/integrations/commercial/durosuite/api.py +1546 -0
  139. regscale/integrations/commercial/durosuite/process_devices.py +101 -0
  140. regscale/integrations/commercial/durosuite/scanner.py +637 -0
  141. regscale/integrations/commercial/durosuite/variables.py +21 -0
  142. regscale/integrations/commercial/ecr.py +90 -0
  143. regscale/integrations/commercial/gcp/__init__.py +237 -0
  144. regscale/integrations/commercial/gcp/auth.py +96 -0
  145. regscale/integrations/commercial/gcp/control_tests.py +238 -0
  146. regscale/integrations/commercial/gcp/variables.py +18 -0
  147. regscale/integrations/commercial/gitlab.py +332 -0
  148. regscale/integrations/commercial/grype.py +165 -0
  149. regscale/integrations/commercial/ibm.py +90 -0
  150. regscale/integrations/commercial/import_all/__init__.py +0 -0
  151. regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
  152. regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
  153. regscale/integrations/commercial/jira.py +1046 -0
  154. regscale/integrations/commercial/mappings/__init__.py +0 -0
  155. regscale/integrations/commercial/mappings/csf_controls.json +713 -0
  156. regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
  157. regscale/integrations/commercial/nessus/__init__.py +0 -0
  158. regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
  159. regscale/integrations/commercial/nessus/scanner.py +416 -0
  160. regscale/integrations/commercial/nexpose.py +90 -0
  161. regscale/integrations/commercial/okta.py +798 -0
  162. regscale/integrations/commercial/opentext/__init__.py +0 -0
  163. regscale/integrations/commercial/opentext/click.py +99 -0
  164. regscale/integrations/commercial/opentext/scanner.py +143 -0
  165. regscale/integrations/commercial/prisma.py +91 -0
  166. regscale/integrations/commercial/qualys.py +1462 -0
  167. regscale/integrations/commercial/salesforce.py +980 -0
  168. regscale/integrations/commercial/sap/__init__.py +0 -0
  169. regscale/integrations/commercial/sap/click.py +31 -0
  170. regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
  171. regscale/integrations/commercial/sap/sysdig/click.py +57 -0
  172. regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
  173. regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
  174. regscale/integrations/commercial/sap/tenable/click.py +49 -0
  175. regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
  176. regscale/integrations/commercial/servicenow.py +1756 -0
  177. regscale/integrations/commercial/sicura/__init__.py +0 -0
  178. regscale/integrations/commercial/sicura/api.py +855 -0
  179. regscale/integrations/commercial/sicura/commands.py +73 -0
  180. regscale/integrations/commercial/sicura/scanner.py +481 -0
  181. regscale/integrations/commercial/sicura/variables.py +16 -0
  182. regscale/integrations/commercial/snyk.py +90 -0
  183. regscale/integrations/commercial/sonarcloud.py +260 -0
  184. regscale/integrations/commercial/sqlserver.py +369 -0
  185. regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
  186. regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
  187. regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
  188. regscale/integrations/commercial/stigv2/__init__.py +0 -0
  189. regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
  190. regscale/integrations/commercial/stigv2/click_commands.py +95 -0
  191. regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
  192. regscale/integrations/commercial/synqly/__init__.py +0 -0
  193. regscale/integrations/commercial/synqly/assets.py +46 -0
  194. regscale/integrations/commercial/synqly/ticketing.py +132 -0
  195. regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
  196. regscale/integrations/commercial/synqly_jira.py +840 -0
  197. regscale/integrations/commercial/tenablev2/__init__.py +0 -0
  198. regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
  199. regscale/integrations/commercial/tenablev2/click.py +1584 -0
  200. regscale/integrations/commercial/tenablev2/scanner.py +504 -0
  201. regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
  202. regscale/integrations/commercial/tenablev2/utils.py +78 -0
  203. regscale/integrations/commercial/tenablev2/variables.py +17 -0
  204. regscale/integrations/commercial/trivy.py +162 -0
  205. regscale/integrations/commercial/veracode.py +96 -0
  206. regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
  207. regscale/integrations/commercial/wizv2/__init__.py +0 -0
  208. regscale/integrations/commercial/wizv2/click.py +429 -0
  209. regscale/integrations/commercial/wizv2/constants.py +1001 -0
  210. regscale/integrations/commercial/wizv2/issue.py +361 -0
  211. regscale/integrations/commercial/wizv2/models.py +112 -0
  212. regscale/integrations/commercial/wizv2/parsers.py +339 -0
  213. regscale/integrations/commercial/wizv2/sbom.py +115 -0
  214. regscale/integrations/commercial/wizv2/scanner.py +416 -0
  215. regscale/integrations/commercial/wizv2/utils.py +796 -0
  216. regscale/integrations/commercial/wizv2/variables.py +39 -0
  217. regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
  218. regscale/integrations/commercial/xray.py +91 -0
  219. regscale/integrations/integration/__init__.py +2 -0
  220. regscale/integrations/integration/integration.py +26 -0
  221. regscale/integrations/integration/inventory.py +17 -0
  222. regscale/integrations/integration/issue.py +100 -0
  223. regscale/integrations/integration_override.py +149 -0
  224. regscale/integrations/public/__init__.py +103 -0
  225. regscale/integrations/public/cisa.py +641 -0
  226. regscale/integrations/public/criticality_updater.py +70 -0
  227. regscale/integrations/public/emass.py +411 -0
  228. regscale/integrations/public/emass_slcm_import.py +697 -0
  229. regscale/integrations/public/fedramp/__init__.py +0 -0
  230. regscale/integrations/public/fedramp/appendix_parser.py +548 -0
  231. regscale/integrations/public/fedramp/click.py +479 -0
  232. regscale/integrations/public/fedramp/components.py +714 -0
  233. regscale/integrations/public/fedramp/docx_parser.py +259 -0
  234. regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
  235. regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
  236. regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
  237. regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
  238. regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
  239. regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
  240. regscale/integrations/public/fedramp/import_workbook.py +495 -0
  241. regscale/integrations/public/fedramp/inventory_items.py +244 -0
  242. regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
  243. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
  244. regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
  245. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
  246. regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
  247. regscale/integrations/public/fedramp/mappings/user.py +175 -0
  248. regscale/integrations/public/fedramp/mappings/values.py +141 -0
  249. regscale/integrations/public/fedramp/markdown_parser.py +150 -0
  250. regscale/integrations/public/fedramp/metadata.py +689 -0
  251. regscale/integrations/public/fedramp/models/__init__.py +59 -0
  252. regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
  253. regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
  254. regscale/integrations/public/fedramp/parts_mapper.py +107 -0
  255. regscale/integrations/public/fedramp/poam/__init__.py +0 -0
  256. regscale/integrations/public/fedramp/poam/scanner.py +851 -0
  257. regscale/integrations/public/fedramp/properties.py +201 -0
  258. regscale/integrations/public/fedramp/reporting.py +84 -0
  259. regscale/integrations/public/fedramp/resources.py +496 -0
  260. regscale/integrations/public/fedramp/rosetta.py +110 -0
  261. regscale/integrations/public/fedramp/ssp_logger.py +87 -0
  262. regscale/integrations/public/fedramp/system_characteristics.py +922 -0
  263. regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
  264. regscale/integrations/public/fedramp/system_implementation.py +190 -0
  265. regscale/integrations/public/fedramp/xml_utils.py +87 -0
  266. regscale/integrations/public/nist_catalog.py +275 -0
  267. regscale/integrations/public/oscal.py +1946 -0
  268. regscale/integrations/public/otx.py +169 -0
  269. regscale/integrations/scanner_integration.py +2692 -0
  270. regscale/integrations/variables.py +25 -0
  271. regscale/models/__init__.py +7 -0
  272. regscale/models/app_models/__init__.py +5 -0
  273. regscale/models/app_models/catalog_compare.py +213 -0
  274. regscale/models/app_models/click.py +252 -0
  275. regscale/models/app_models/datetime_encoder.py +21 -0
  276. regscale/models/app_models/import_validater.py +321 -0
  277. regscale/models/app_models/mapping.py +260 -0
  278. regscale/models/app_models/pipeline.py +37 -0
  279. regscale/models/click_models.py +413 -0
  280. regscale/models/config.py +154 -0
  281. regscale/models/email_style.css +67 -0
  282. regscale/models/hierarchy.py +8 -0
  283. regscale/models/inspect_models.py +79 -0
  284. regscale/models/integration_models/__init__.py +0 -0
  285. regscale/models/integration_models/amazon_models/__init__.py +0 -0
  286. regscale/models/integration_models/amazon_models/inspector.py +262 -0
  287. regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
  288. regscale/models/integration_models/aqua.py +247 -0
  289. regscale/models/integration_models/azure_alerts.py +255 -0
  290. regscale/models/integration_models/base64.py +23 -0
  291. regscale/models/integration_models/burp.py +433 -0
  292. regscale/models/integration_models/burp_models.py +128 -0
  293. regscale/models/integration_models/cisa_kev_data.json +19333 -0
  294. regscale/models/integration_models/defender_data.py +93 -0
  295. regscale/models/integration_models/defenderimport.py +143 -0
  296. regscale/models/integration_models/drf.py +443 -0
  297. regscale/models/integration_models/ecr_models/__init__.py +0 -0
  298. regscale/models/integration_models/ecr_models/data.py +69 -0
  299. regscale/models/integration_models/ecr_models/ecr.py +239 -0
  300. regscale/models/integration_models/flat_file_importer.py +1079 -0
  301. regscale/models/integration_models/grype_import.py +247 -0
  302. regscale/models/integration_models/ibm.py +126 -0
  303. regscale/models/integration_models/implementation_results.py +85 -0
  304. regscale/models/integration_models/nexpose.py +140 -0
  305. regscale/models/integration_models/prisma.py +202 -0
  306. regscale/models/integration_models/qualys.py +720 -0
  307. regscale/models/integration_models/qualys_scanner.py +160 -0
  308. regscale/models/integration_models/sbom/__init__.py +0 -0
  309. regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
  310. regscale/models/integration_models/send_reminders.py +620 -0
  311. regscale/models/integration_models/snyk.py +155 -0
  312. regscale/models/integration_models/synqly_models/__init__.py +0 -0
  313. regscale/models/integration_models/synqly_models/capabilities.json +1 -0
  314. regscale/models/integration_models/synqly_models/connector_types.py +22 -0
  315. regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
  316. regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
  317. regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
  318. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
  319. regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
  320. regscale/models/integration_models/synqly_models/param.py +72 -0
  321. regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
  322. regscale/models/integration_models/synqly_models/tenants.py +39 -0
  323. regscale/models/integration_models/tenable_models/__init__.py +0 -0
  324. regscale/models/integration_models/tenable_models/integration.py +187 -0
  325. regscale/models/integration_models/tenable_models/models.py +513 -0
  326. regscale/models/integration_models/trivy_import.py +231 -0
  327. regscale/models/integration_models/veracode.py +217 -0
  328. regscale/models/integration_models/xray.py +135 -0
  329. regscale/models/locking.py +100 -0
  330. regscale/models/platform.py +110 -0
  331. regscale/models/regscale_models/__init__.py +67 -0
  332. regscale/models/regscale_models/assessment.py +570 -0
  333. regscale/models/regscale_models/assessment_plan.py +52 -0
  334. regscale/models/regscale_models/asset.py +567 -0
  335. regscale/models/regscale_models/asset_mapping.py +190 -0
  336. regscale/models/regscale_models/case.py +42 -0
  337. regscale/models/regscale_models/catalog.py +261 -0
  338. regscale/models/regscale_models/cci.py +46 -0
  339. regscale/models/regscale_models/change.py +167 -0
  340. regscale/models/regscale_models/checklist.py +372 -0
  341. regscale/models/regscale_models/comment.py +49 -0
  342. regscale/models/regscale_models/compliance_settings.py +112 -0
  343. regscale/models/regscale_models/component.py +412 -0
  344. regscale/models/regscale_models/component_mapping.py +65 -0
  345. regscale/models/regscale_models/control.py +38 -0
  346. regscale/models/regscale_models/control_implementation.py +1128 -0
  347. regscale/models/regscale_models/control_objective.py +261 -0
  348. regscale/models/regscale_models/control_parameter.py +100 -0
  349. regscale/models/regscale_models/control_test.py +34 -0
  350. regscale/models/regscale_models/control_test_plan.py +75 -0
  351. regscale/models/regscale_models/control_test_result.py +52 -0
  352. regscale/models/regscale_models/custom_field.py +245 -0
  353. regscale/models/regscale_models/data.py +109 -0
  354. regscale/models/regscale_models/data_center.py +40 -0
  355. regscale/models/regscale_models/deviation.py +203 -0
  356. regscale/models/regscale_models/email.py +97 -0
  357. regscale/models/regscale_models/evidence.py +47 -0
  358. regscale/models/regscale_models/evidence_mapping.py +40 -0
  359. regscale/models/regscale_models/facility.py +59 -0
  360. regscale/models/regscale_models/file.py +382 -0
  361. regscale/models/regscale_models/filetag.py +37 -0
  362. regscale/models/regscale_models/form_field_value.py +94 -0
  363. regscale/models/regscale_models/group.py +169 -0
  364. regscale/models/regscale_models/implementation_objective.py +335 -0
  365. regscale/models/regscale_models/implementation_option.py +275 -0
  366. regscale/models/regscale_models/implementation_role.py +33 -0
  367. regscale/models/regscale_models/incident.py +177 -0
  368. regscale/models/regscale_models/interconnection.py +43 -0
  369. regscale/models/regscale_models/issue.py +1176 -0
  370. regscale/models/regscale_models/leveraged_authorization.py +125 -0
  371. regscale/models/regscale_models/line_of_inquiry.py +52 -0
  372. regscale/models/regscale_models/link.py +205 -0
  373. regscale/models/regscale_models/meta_data.py +64 -0
  374. regscale/models/regscale_models/mixins/__init__.py +0 -0
  375. regscale/models/regscale_models/mixins/parent_cache.py +124 -0
  376. regscale/models/regscale_models/module.py +224 -0
  377. regscale/models/regscale_models/modules.py +191 -0
  378. regscale/models/regscale_models/objective.py +14 -0
  379. regscale/models/regscale_models/parameter.py +87 -0
  380. regscale/models/regscale_models/ports_protocol.py +81 -0
  381. regscale/models/regscale_models/privacy.py +89 -0
  382. regscale/models/regscale_models/profile.py +50 -0
  383. regscale/models/regscale_models/profile_link.py +68 -0
  384. regscale/models/regscale_models/profile_mapping.py +124 -0
  385. regscale/models/regscale_models/project.py +63 -0
  386. regscale/models/regscale_models/property.py +278 -0
  387. regscale/models/regscale_models/question.py +85 -0
  388. regscale/models/regscale_models/questionnaire.py +87 -0
  389. regscale/models/regscale_models/questionnaire_instance.py +177 -0
  390. regscale/models/regscale_models/rbac.py +132 -0
  391. regscale/models/regscale_models/reference.py +86 -0
  392. regscale/models/regscale_models/regscale_model.py +1643 -0
  393. regscale/models/regscale_models/requirement.py +29 -0
  394. regscale/models/regscale_models/risk.py +274 -0
  395. regscale/models/regscale_models/sbom.py +54 -0
  396. regscale/models/regscale_models/scan_history.py +436 -0
  397. regscale/models/regscale_models/search.py +53 -0
  398. regscale/models/regscale_models/security_control.py +132 -0
  399. regscale/models/regscale_models/security_plan.py +204 -0
  400. regscale/models/regscale_models/software_inventory.py +159 -0
  401. regscale/models/regscale_models/stake_holder.py +64 -0
  402. regscale/models/regscale_models/stig.py +647 -0
  403. regscale/models/regscale_models/supply_chain.py +152 -0
  404. regscale/models/regscale_models/system_role.py +188 -0
  405. regscale/models/regscale_models/system_role_external_assignment.py +40 -0
  406. regscale/models/regscale_models/tag.py +37 -0
  407. regscale/models/regscale_models/tag_mapping.py +19 -0
  408. regscale/models/regscale_models/task.py +133 -0
  409. regscale/models/regscale_models/threat.py +196 -0
  410. regscale/models/regscale_models/user.py +175 -0
  411. regscale/models/regscale_models/user_group.py +55 -0
  412. regscale/models/regscale_models/vulnerability.py +242 -0
  413. regscale/models/regscale_models/vulnerability_mapping.py +162 -0
  414. regscale/models/regscale_models/workflow.py +55 -0
  415. regscale/models/regscale_models/workflow_action.py +34 -0
  416. regscale/models/regscale_models/workflow_instance.py +269 -0
  417. regscale/models/regscale_models/workflow_instance_step.py +114 -0
  418. regscale/models/regscale_models/workflow_template.py +58 -0
  419. regscale/models/regscale_models/workflow_template_step.py +45 -0
  420. regscale/regscale.py +815 -0
  421. regscale/utils/__init__.py +7 -0
  422. regscale/utils/b64conversion.py +14 -0
  423. regscale/utils/click_utils.py +118 -0
  424. regscale/utils/decorators.py +48 -0
  425. regscale/utils/dict_utils.py +59 -0
  426. regscale/utils/files.py +79 -0
  427. regscale/utils/fxns.py +30 -0
  428. regscale/utils/graphql_client.py +113 -0
  429. regscale/utils/lists.py +16 -0
  430. regscale/utils/numbers.py +12 -0
  431. regscale/utils/shell.py +148 -0
  432. regscale/utils/string.py +121 -0
  433. regscale/utils/synqly_utils.py +165 -0
  434. regscale/utils/threading/__init__.py +8 -0
  435. regscale/utils/threading/threadhandler.py +131 -0
  436. regscale/utils/threading/threadsafe_counter.py +47 -0
  437. regscale/utils/threading/threadsafe_dict.py +242 -0
  438. regscale/utils/threading/threadsafe_list.py +83 -0
  439. regscale/utils/version.py +104 -0
  440. regscale/validation/__init__.py +0 -0
  441. regscale/validation/address.py +37 -0
  442. regscale/validation/record.py +48 -0
  443. regscale/visualization/__init__.py +5 -0
  444. regscale/visualization/click.py +34 -0
  445. regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
  446. regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
  447. regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
  448. regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
  449. regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
  450. regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
  451. tests/fixtures/__init__.py +2 -0
  452. tests/fixtures/api.py +87 -0
  453. tests/fixtures/models.py +91 -0
  454. tests/fixtures/test_fixture.py +144 -0
  455. tests/mocks/__init__.py +0 -0
  456. tests/mocks/objects.py +3 -0
  457. tests/mocks/response.py +32 -0
  458. tests/mocks/xml.py +13 -0
  459. tests/regscale/__init__.py +0 -0
  460. tests/regscale/core/__init__.py +0 -0
  461. tests/regscale/core/test_api.py +232 -0
  462. tests/regscale/core/test_app.py +406 -0
  463. tests/regscale/core/test_login.py +37 -0
  464. tests/regscale/core/test_logz.py +66 -0
  465. tests/regscale/core/test_sbom_generator.py +87 -0
  466. tests/regscale/core/test_validation_utils.py +163 -0
  467. tests/regscale/core/test_version.py +78 -0
  468. tests/regscale/models/__init__.py +0 -0
  469. tests/regscale/models/test_asset.py +71 -0
  470. tests/regscale/models/test_config.py +26 -0
  471. tests/regscale/models/test_control_implementation.py +27 -0
  472. tests/regscale/models/test_import.py +97 -0
  473. tests/regscale/models/test_issue.py +36 -0
  474. tests/regscale/models/test_mapping.py +52 -0
  475. tests/regscale/models/test_platform.py +31 -0
  476. tests/regscale/models/test_regscale_model.py +346 -0
  477. tests/regscale/models/test_report.py +32 -0
  478. tests/regscale/models/test_tenable_integrations.py +118 -0
  479. tests/regscale/models/test_user_model.py +121 -0
  480. tests/regscale/test_about.py +19 -0
  481. tests/regscale/test_authorization.py +65 -0
@@ -0,0 +1,454 @@
1
+ """Provide analysis functions for regscale."""
2
+
3
+ import ast
4
+ import itertools
5
+ import math
6
+ import os
7
+
8
+ import git
9
+ import matplotlib.pyplot as plt
10
+ from radon.complexity import cc_visit
11
+ from radon.metrics import h_visit
12
+ from radon.raw import analyze
13
+
14
+ from regscale.core.app.logz import create_logger
15
+
16
+ INIT_FILE = "__init__.py"
17
+
18
+
19
+ def find_py_files(root_folder: str) -> list[str]:
20
+ """Find all Python files in a given root folder and its subdirectories
21
+
22
+ :param str root_folder: the root folder to search
23
+ :return: a list of Python files
24
+ :rtype: list[str]
25
+ """
26
+ py_files = []
27
+ for root, dirs, files in os.walk(root_folder):
28
+ py_files.extend([os.path.join(root, f) for f in files if f.endswith(".py")])
29
+ return py_files
30
+
31
+
32
+ def analyze_git(folder_path: str = ".", subfolder: str = "regscale") -> dict:
33
+ """Analyze a git repository at the repo_path
34
+
35
+ :param str folder_path: the path to the folder to analyze
36
+ :param str subfolder: the subfolder to analyze
37
+ :return: a dictionary of git information
38
+ :rtype: dict
39
+ """
40
+ log = create_logger()
41
+ try:
42
+ # initialize a git repository object
43
+ repo = git.Repo(folder_path)
44
+ except git.InvalidGitRepositoryError:
45
+ log.error(f"{folder_path} is not a valid git repository")
46
+ return {}
47
+ # initialize the metrics dictionary
48
+ git_metrics = {}
49
+ full_subfolder_path = os.path.join(folder_path, subfolder)
50
+ py_files = find_py_files(full_subfolder_path)
51
+ for py_file in py_files:
52
+ if INIT_FILE in py_file:
53
+ continue
54
+ commit_count = 0
55
+ contributors = set()
56
+ file_path_in_repo = os.path.relpath(py_file, folder_path)
57
+ for commit in repo.iter_commits(paths=file_path_in_repo):
58
+ commit_count += 1
59
+ contributors.add(commit.author.name)
60
+ file_metrics = {
61
+ "commit_count": commit_count,
62
+ "contributors": list(contributors),
63
+ "num_contributors": len(contributors),
64
+ "lines": len(open(py_file).readlines()),
65
+ "path": file_path_in_repo,
66
+ }
67
+ # get the date of the last update for this file
68
+ last_commit = next(repo.iter_commits(paths=file_path_in_repo), None)
69
+ file_metrics["last_update_date"] = (
70
+ last_commit.committed_datetime.strftime("%Y-%m-%d %H:%M") if last_commit else None
71
+ )
72
+ git_metrics[py_file[2:]] = file_metrics
73
+ return git_metrics
74
+
75
+
76
+ def parse_comments(file_path: str) -> int:
77
+ """Parse the number of comments in a given file
78
+
79
+ :param str file_path: the path to the file to parse
80
+ :return: the number of comments
81
+ :rtype: int
82
+ """
83
+ comments = 0
84
+ with open(file_path, "r") as f:
85
+ for line in f:
86
+ if line.strip().startswith("#"):
87
+ comments += 1
88
+ continue
89
+ if '"""' in line or "'''" in line:
90
+ comments += 0.5
91
+ elif "# " in line or " #" in line:
92
+ comments += 1
93
+ return comments
94
+
95
+
96
+ def parse_code(file_path: str) -> dict:
97
+ """Parse a python file and return a dictionary of the parsed code metrics
98
+
99
+ :param str file_path: the path to the file to parse
100
+ :return: a dictionary of the parsed code metrics
101
+ :rtype: dict
102
+ """
103
+ comments = parse_comments(file_path)
104
+ # set the metrics
105
+ metrics = {
106
+ "Assignments": 0,
107
+ "Comments": comments,
108
+ "Conditionals": 0,
109
+ "Classes": 0,
110
+ "Functions": 0,
111
+ "Imports": 0,
112
+ "Loops": 0,
113
+ "Statements": 0,
114
+ }
115
+ with open(file_path, "r") as f:
116
+ tree = ast.parse(f.read(), filename=file_path)
117
+ # Generate the AST
118
+ # Walk through all nodes in the AST
119
+ for node in ast.walk(tree):
120
+ if isinstance(node, ast.stmt):
121
+ metrics["Statements"] += 1
122
+ if isinstance(node, ast.FunctionDef):
123
+ metrics["Functions"] += 1
124
+ elif isinstance(node, ast.Assign):
125
+ metrics["Assignments"] += 1
126
+ elif isinstance(node, ast.ClassDef):
127
+ metrics["Classes"] += 1
128
+ elif isinstance(node, (ast.Import, ast.ImportFrom)):
129
+ metrics["Imports"] += 1
130
+ elif isinstance(node, (ast.While, ast.For)):
131
+ metrics["Loops"] += 1
132
+ elif isinstance(node, ast.If):
133
+ metrics["Conditionals"] += 1
134
+ metrics["Statements"] -= metrics["Imports"] # imports are statements too
135
+ return metrics
136
+
137
+
138
+ def find_global_variables(file_path: str) -> dict:
139
+ """Find all global variables in a given python file
140
+
141
+ :param str file_path: the path to the file to analyze
142
+ :return: a list of global variables
143
+ :rtype: dict
144
+ """
145
+ with open(file_path, "r") as f:
146
+ code = f.read()
147
+ # Generate the AST
148
+ tree = ast.parse(code)
149
+ global_variables = []
150
+ num_globals = 0
151
+ # Walk through all nodes in the AST
152
+ for node in ast.walk(tree):
153
+ if isinstance(node, ast.Assign):
154
+ global_variables.extend(target.id for target in node.targets if isinstance(target, ast.Name))
155
+ num_globals += 1
156
+ return {"Global Variables": global_variables, "Num Globals": num_globals}
157
+
158
+
159
+ def analyze_code_smells(
160
+ file_path: str,
161
+ long_method_threshold: int = 20,
162
+ param_threshold: int = 4,
163
+ nested_loop_threshold: int = 3,
164
+ ) -> dict:
165
+ """Analyze code smells for a given python file
166
+
167
+ :param str file_path: the path to the file to analyze
168
+ :param int long_method_threshold: the threshold for a long method
169
+ :param int param_threshold: the threshold for a long parameter list
170
+ :param int nested_loop_threshold: the threshold for nested loops
171
+ :return: a dictionary of code smells
172
+ :rtype: dict
173
+ """
174
+ long_methods = []
175
+ many_param_methods = []
176
+ nested_loops = []
177
+ global_vars = []
178
+ with open(file_path, "r") as f:
179
+ tree = ast.parse(f.read(), filename=file_path)
180
+ for node in ast.walk(tree):
181
+ if isinstance(node, ast.FunctionDef):
182
+ # find long methods
183
+ line_count = node.end_lineno - node.lineno + 1
184
+ if line_count > long_method_threshold:
185
+ long_methods.append(node.name)
186
+ # too many parameters
187
+ if len(node.args.args) > param_threshold:
188
+ many_param_methods.append({"name": node.name, "param_count": len(node.args.args)})
189
+ # nested loops
190
+ elif isinstance(node, (ast.For, ast.While)):
191
+ nested_count = sum(isinstance(_, (ast.For, ast.While)) for _ in ast.walk(node))
192
+ if nested_count > nested_loop_threshold:
193
+ nested_loops.append({"lineno": node.lineno, "nested_count": nested_count})
194
+ # find global variables
195
+ elif isinstance(node, ast.Global):
196
+ global_vars.extend(node.names)
197
+ return {
198
+ "Long Methods": long_methods,
199
+ "Many param Methods": many_param_methods,
200
+ "Nested Loops": nested_loops,
201
+ "Global Vars": global_vars,
202
+ "Num Long Methods": len(long_methods),
203
+ "Num Many Param Methods": len(many_param_methods),
204
+ "Num Nested Loops": len(nested_loops),
205
+ "Num Global Vars": len(global_vars),
206
+ }
207
+
208
+
209
+ def analyze_code_files(root_folder: str = "regscale") -> dict:
210
+ """Analyze all Python files in a given root folder and its subdirectories
211
+
212
+ :param str root_folder: the root folder to search
213
+ :return: a dictionary of code metrics
214
+ :rtype: dict
215
+ """
216
+ py_files = find_py_files(root_folder)
217
+ return {
218
+ file_path: parse_code(file_path)
219
+ | analyze_text_search(file_path)
220
+ | analyze_complexity_metrics(file_path)
221
+ | find_global_variables(file_path)
222
+ | analyze_fan_metrics(file_path)
223
+ | analyze_code_smells(file_path)
224
+ for file_path in py_files
225
+ if INIT_FILE not in file_path
226
+ }
227
+
228
+
229
+ def analyze_text_search(file_path: str) -> dict:
230
+ """Search for FIXMEs and TODOs in a given python file
231
+
232
+ :param str file_path: the path to the file to search
233
+ :return: a dictionary of FIXMEs and TODOs
234
+ :rtype: dict
235
+ """
236
+ metrics = {
237
+ "FIXMEs": 0,
238
+ "TODOs": 0,
239
+ "TODO Density": 0.0,
240
+ "FIXME Density": 0.0,
241
+ }
242
+ with open(file_path, "r") as f:
243
+ lines = len(f.readlines())
244
+ f.seek(0)
245
+ for line in f:
246
+ if "# FIXME" in line.upper():
247
+ metrics["FIXMEs"] += 1
248
+ if "# TODO" in line.upper():
249
+ metrics["TODOs"] += 1
250
+ metrics["FIXME Density"] = metrics["FIXMEs"] / lines if lines else 0.0
251
+ metrics["TODO Density"] = metrics["TODOs"] / lines if lines else 0.0
252
+ return metrics
253
+
254
+
255
+ def analyze_complexity_metrics(file_path: str) -> dict:
256
+ """Analyze the complexity metrics for a given python file
257
+
258
+ :param str file_path: the path to the file to analyze
259
+ :return: a dictionary of complexity metrics
260
+ :rtype: dict
261
+ """
262
+ with open(file_path, "r") as f:
263
+ code = f.read()
264
+ # Radon metrics
265
+ raw_metrics = analyze(code)
266
+ halstead_metrics = h_visit(code)
267
+ cyclomatic_metrics = cc_visit(code)
268
+ complexity = sum((complexity_.complexity for complexity_ in cyclomatic_metrics))
269
+ return {
270
+ "Lines of Code": raw_metrics.loc,
271
+ "Lines of Comments": raw_metrics.comments,
272
+ "Maintainability Index": calculate_maintainability_index(
273
+ volume=halstead_metrics.total.volume,
274
+ complexity=complexity,
275
+ loc=raw_metrics.loc,
276
+ ),
277
+ "Halstead Vocabulary": halstead_metrics.total.vocabulary,
278
+ "Halstead Difficulty": halstead_metrics.total.difficulty,
279
+ "Halstead Effort": halstead_metrics.total.effort,
280
+ "Halstead Volume": halstead_metrics.total.volume,
281
+ "Cyclomatic Complexity": complexity,
282
+ "Complexity/LOC Ratio": (complexity / raw_metrics.loc if raw_metrics.loc else 0.0),
283
+ }
284
+
285
+
286
+ def calculate_maintainability_index(volume: float, complexity: float, loc: int) -> float:
287
+ """Calculate the maintainability index
288
+ MI = 171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code)
289
+
290
+ :param float volume: the Halstead Volume
291
+ :param float complexity: the Cyclomatic Complexity
292
+ :param int loc: the Lines of Code
293
+ :return: the maintainability index
294
+ :rtype: float
295
+ """
296
+ try:
297
+ return 171 - 5.2 * math.log(volume) - 0.23 * complexity - 16.2 * math.log(loc)
298
+ except ValueError:
299
+ return 0.0
300
+
301
+
302
+ def analyze_fan_metrics(file_path: str) -> dict:
303
+ """Analyze Fan-in and Fan-out metrics for each function in a Python file
304
+
305
+ :param str file_path: the path to the file to analyze
306
+ :return: a dictionary of Fan-in and Fan-out metrics
307
+ :rtype: dict
308
+ """
309
+ fan_in = {}
310
+ fan_out = {}
311
+ with open(file_path, "r") as f:
312
+ tree = ast.parse(f.read(), filename=file_path)
313
+
314
+ for node in ast.walk(tree):
315
+ if isinstance(node, ast.FunctionDef):
316
+ fan_out[node.name] = 0
317
+ for sub_node in ast.walk(node):
318
+ if isinstance(sub_node, ast.Call) and isinstance(sub_node.func, ast.Name):
319
+ fan_out[node.name] += 1
320
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
321
+ func_name = node.func.id
322
+ fan_in[func_name] = fan_in.get(func_name, 0) + 1
323
+ return {
324
+ "Fan In": fan_in,
325
+ "Fan Out": fan_out,
326
+ "Num Fan In": len(fan_in),
327
+ "Num Fan Out": len(fan_out),
328
+ }
329
+
330
+
331
+ def generate_heatmap(
332
+ data: dict,
333
+ metric: str,
334
+ figsize: tuple = (50, 50),
335
+ font_size: int = 7,
336
+ show: bool = False,
337
+ ) -> plt.Figure:
338
+ """Generate a heatmap of the given metric
339
+
340
+ :param dict data: the data to plot
341
+ :param str metric: the metric to plot
342
+ :param tuple figsize: the figure size
343
+ :param int font_size: the font size
344
+ :param bool show: whether to show the plot
345
+ :return: matplotlib.pyplot.Figure
346
+ :rtype: plt.Figure
347
+ """
348
+ import numpy as np # Optimize import performance
349
+
350
+ # Collect file names and their corresponding metric values
351
+ file_names = list(data.keys())
352
+ values = [item.get(metric, 0) for item in data.values()]
353
+
354
+ # Calculate grid dimensions based on the number of files
355
+ grid_size = int(np.ceil(np.sqrt(len(file_names))))
356
+
357
+ # Create an empty grid
358
+ grid = np.zeros((grid_size, grid_size))
359
+
360
+ # Fill in the grid with metric values
361
+ for i, j in itertools.product(range(grid_size), range(grid_size)):
362
+ index = i * grid_size + j
363
+ if index < len(values):
364
+ grid[i, j] = values[index]
365
+
366
+ # Generate the heatmap with increased figure size
367
+ fig, ax = plt.subplots(figsize=figsize)
368
+ cax = ax.matshow(grid, cmap="coolwarm")
369
+ colorbar = fig.colorbar(cax)
370
+ colorbar.ax.tick_params(labelsize=40)
371
+
372
+ # Remove tick marks
373
+ ax.set_xticks([])
374
+ ax.set_yticks([])
375
+
376
+ # Label the squares with file names, adjusting font size for readability
377
+ for i, j in itertools.product(range(grid_size), range(grid_size)):
378
+ index = i * grid_size + j
379
+ if index < len(file_names):
380
+ file_name = str(file_names[index])
381
+ metric_value = "{:.2f}".format(values[index]) if "." in str(values[index]) else str(values[index])
382
+ plt.text(
383
+ j,
384
+ i,
385
+ f"{file_name}\n{metric_value}",
386
+ va="center",
387
+ ha="center",
388
+ fontsize=font_size,
389
+ wrap=True,
390
+ )
391
+ plt.title(metric, fontsize=70)
392
+ if show:
393
+ plt.show()
394
+ return plt.gcf()
395
+
396
+
397
+ def generate_barplot(
398
+ data: dict,
399
+ metric: str,
400
+ top_n: int = 20,
401
+ figsize: tuple = (50, 50),
402
+ show: bool = False,
403
+ ) -> plt.Figure:
404
+ """Generate a barplot of the given metric
405
+
406
+ :param dict data: the data to plot
407
+ :param str metric: the metric to plot
408
+ :param int top_n: the number of top items to plot
409
+ :param tuple figsize: the figure size
410
+ :param bool show: whether to show the plot
411
+ :return: matplotlib.pyplot.Figure
412
+ :rtype: plt.Figure
413
+ """
414
+ values = {file: item.get(metric, 0) for file, item in data.items() if not file.endswith(INIT_FILE)}
415
+ if metric == "Maintainability Index":
416
+ # sort by the lowest values for Maintainability Index
417
+ filtered_values = {file: value for file, value in values.items() if int(value) != 0}
418
+ # also filter out any files with a Maintainability Index of 0
419
+ sorted_values = dict(sorted(filtered_values.items(), key=lambda item: item[1])[:top_n])
420
+ else:
421
+ # sort by the highest values for all other metrics
422
+ sorted_values = dict(sorted(values.items(), key=lambda item: item[1], reverse=True)[:top_n])
423
+ _, ax = plt.subplots(figsize=figsize)
424
+ bars = ax.bar(sorted_values.keys(), sorted_values.values())
425
+ for bar in bars:
426
+ height = bar.get_height()
427
+ ax.text(
428
+ bar.get_x() + bar.get_width() / 2.0,
429
+ height,
430
+ str(round(height, 2)),
431
+ va="bottom",
432
+ ha="center",
433
+ fontsize=30,
434
+ )
435
+ plt.xticks(rotation=90, ha="right", fontsize=22)
436
+ plt.yticks(fontsize=40)
437
+ plt.title(f"Top 20 {metric}", fontsize=70)
438
+ plt.xlabel("File", fontsize=40)
439
+ plt.ylabel(metric, fontsize=40)
440
+ if show:
441
+ plt.show()
442
+ return plt.gcf()
443
+
444
+
445
+ def combine_git_and_code_metrics() -> dict:
446
+ """Combine the git and code metrics
447
+ :return: a dictionary of combined git and code metrics
448
+ :rtype: dict
449
+ """
450
+ git_metrics = analyze_git()
451
+ code_metrics = analyze_code_files()
452
+ return {
453
+ key: {**git_metrics.get(key, {}), **code_metrics.get(key, {})} for key in set(git_metrics) | set(code_metrics)
454
+ }
regscale/dev/cli.py ADDED
@@ -0,0 +1,235 @@
1
+ """CLI for regscale-dev commands."""
2
+
3
+ import contextlib
4
+ import os
5
+ import sys
6
+
7
+ import click
8
+ from rich.console import Console
9
+
10
+
11
+ @click.group()
12
+ def cli() -> click.Group:
13
+ """RegScale-Dev CLI."""
14
+ pass
15
+
16
+
17
+ @cli.command()
18
+ @click.option("--iterations", default=100, help="The number of times to run the function")
19
+ def profile(iterations: int) -> None:
20
+ """Profile the CLI."""
21
+ from regscale.dev.profiling import profile_about_command, profile_my_function
22
+
23
+ profile_my_function(profile_about_command, iterations=iterations)
24
+ console = Console()
25
+ console.print(
26
+ "Profiling complete, you can view the file in [yellow]profile_stats.csv[reset] or [yellow]profile_stats.pstat[reset]"
27
+ )
28
+
29
+
30
+ @cli.command()
31
+ @click.option("--iterations", default=100, help="The number of times to run the function")
32
+ @click.option("--no-output", is_flag=True, help="Output in dictionary form")
33
+ def calculate_start_time(iterations: int, no_output: bool) -> None:
34
+ """Calculate the start time for the CLI."""
35
+ from regscale.dev.profiling import calculate_load_times
36
+
37
+ calculate_load_times(iterations=iterations, no_output=no_output)
38
+
39
+
40
+ @cli.command()
41
+ @click.option("--raw", is_flag=True, help="Output raw results")
42
+ def calculate_import_time(raw: bool) -> None:
43
+ """Calculate the import time for the CLI."""
44
+ from regscale.dev.profiling import calculate_cli_import_time
45
+
46
+ load_time = calculate_cli_import_time()
47
+ if raw:
48
+ print(load_time)
49
+ else:
50
+ console = Console()
51
+ console.print(f"It took {load_time:.6f} seconds to import the CLI.")
52
+
53
+
54
+ @cli.command()
55
+ @click.option("--csv", is_flag=True, help="Output raw results to `analysis.csv`")
56
+ def analyze(csv: bool) -> None:
57
+ """Analyze the CLI codebase and generate an optional PDF report or a CSV as well as raw JSON.
58
+ This command must be run from the root of the git repository.
59
+ The code metrics generated include git commits, Halstead Metrics, and Cyclomatic Complexity in addition to
60
+ a variety of other metrics useful for analyzing the codebase.
61
+ """
62
+ if not os.path.exists(".git"):
63
+ print("This command must be run from the root of the repository.")
64
+ sys.exit(1)
65
+ import gc
66
+ import json
67
+ from collections import OrderedDict, defaultdict
68
+
69
+ import matplotlib.pyplot as plt
70
+ from matplotlib.backends.backend_pdf import PdfPages
71
+ from rich.console import Console
72
+ from rich.progress import track
73
+
74
+ from regscale.dev.analysis import analyze_code_files, analyze_git, generate_barplot, generate_heatmap
75
+ from regscale.utils.numbers import is_number
76
+
77
+ console = Console()
78
+ console.print("[yellow]Analyzing RegScale-CLI...")
79
+ git_metrics = analyze_git()
80
+ code_metrics = analyze_code_files()
81
+ raw_data = {
82
+ key: {**git_metrics.get(key, {}), **code_metrics.get(key, {})} for key in set(git_metrics) | set(code_metrics)
83
+ }
84
+ data = OrderedDict(sorted(raw_data.items()))
85
+ # Sample data for one file to identify numeric metrics
86
+ sample_data = next(iter(data.values()), {})
87
+ json.dump(data, open("analysis.json", "w"))
88
+ if csv:
89
+ import pandas as pd # Optimize import performance
90
+
91
+ df = pd.DataFrame(data)
92
+ df = df.transpose()
93
+ df.to_csv("analysis.csv")
94
+ console.print("[green]Done! You can view the raw analysis in analysis.csv or in analysis.json.")
95
+ sys.exit(0)
96
+ metrics_to_track = list(filter(lambda key: is_number(sample_data.get(key)), sample_data.keys()))
97
+ # Aggregate metrics by directory with progress tracking
98
+ aggregated_data = defaultdict(lambda: defaultdict(float))
99
+ for file, metrics in track(data.items(), description="[cyan]Aggregating metrics..."):
100
+ directory = "/".join(file.split("/")[:-1])
101
+ for metric, value in metrics.items():
102
+ if metric in metrics_to_track:
103
+ aggregated_data[directory][metric] += value
104
+ with PdfPages("Metrics Report.pdf") as pdf:
105
+ for metric in track(metrics_to_track, description="[cyan]Generating heatmaps..."):
106
+ plot = generate_heatmap(data, metric)
107
+ plt.tight_layout()
108
+ pdf.savefig(figure=plot, dpi=300)
109
+ plt.close(plot)
110
+ gc.collect()
111
+ for metric in track(metrics_to_track, description="[cyan]Generating barplots..."):
112
+ plot = generate_barplot(data, metric)
113
+ plt.tight_layout()
114
+ pdf.savefig(figure=plot, dpi=300)
115
+ plt.close(plot)
116
+ gc.collect()
117
+ console.print("[yellow]Generating aggregated metrics graphics...")
118
+ plt.figure(figsize=(50, 50))
119
+ plt.axis("tight")
120
+ plt.axis("off")
121
+ selected_metrics = [
122
+ "commit_count",
123
+ "Halstead Volume",
124
+ "Cyclomatic Complexity",
125
+ "Halstead Effort",
126
+ ]
127
+ table_data: list = [] # initialize an empty list to collect table_data
128
+ for file, val in track(data.items(), description="[cyan]Generating table..."):
129
+ row = [file, *[val.get(metric, "N/A") for metric in selected_metrics]]
130
+ table_data.append(row)
131
+ plt.table(
132
+ cellText=table_data,
133
+ colLabels=["File"] + selected_metrics,
134
+ cellLoc="center",
135
+ loc="center",
136
+ )
137
+ pdf.savefig()
138
+ gc.collect()
139
+ console.print("[yellow]Generating aggregated metrics graphics...")
140
+ for metric in track(metrics_to_track, description="[cyan]Generating aggregated heatmaps..."):
141
+ heatmap_plot = generate_heatmap(aggregated_data, metric, figsize=(20, 20), font_size=10)
142
+ plt.tight_layout()
143
+ pdf.savefig(figure=heatmap_plot, dpi=150)
144
+ plt.close(heatmap_plot)
145
+ gc.collect()
146
+ console.print("[red]Writing PDF to file, this may take some time . . . ")
147
+ console.print("[green]Done! You can view the raw analysis in analysis.json and the report in Metrics Report.pdf.")
148
+
149
+
150
+ @cli.command()
151
+ @click.option("--hide_source", is_flag=True, help="Hide the source code link in the generated documentation")
152
+ def make_docs(hide_source: bool) -> None:
153
+ """Generate documentation for the CLI."""
154
+ from regscale.dev.docs import generate_docs
155
+
156
+ generate_docs(hide_source=hide_source)
157
+
158
+
159
+ with contextlib.suppress(ImportError, SystemExit):
160
+ from regscale.airflow.azure.cli import cli as azure_cli
161
+
162
+ cli.add_command(azure_cli)
163
+
164
+
165
+ @cli.command(name="generate_airflow_dags")
166
+ def generate_airflow_dags() -> None:
167
+ """Generate Airflow DAGs for the CLI."""
168
+ from regscale.dev.code_gen import generate_dags
169
+
170
+ generate_dags()
171
+
172
+
173
+ @cli.command(name="generate_click_connectors")
174
+ def generate_click_connectors() -> None:
175
+ """Generate Click Connectors for the CLI."""
176
+ from regscale.dev.code_gen import generate_click_connectors
177
+
178
+ generate_click_connectors()
179
+
180
+
181
+ @cli.command(name="update_docs")
182
+ @click.option(
183
+ "--readme_token",
184
+ "-rt",
185
+ default=os.getenv("README_TOKEN"),
186
+ help="The token for the readme.io API",
187
+ type=click.STRING,
188
+ )
189
+ @click.option(
190
+ "--confluence_user",
191
+ "-cu",
192
+ default=os.getenv("CONFLUENCE_USER"),
193
+ help="The username for the confluence instance",
194
+ type=click.STRING,
195
+ )
196
+ @click.option(
197
+ "--confluence_token",
198
+ "-ct",
199
+ default=os.getenv("CONFLUENCE_TOKEN"),
200
+ help="The token for the confluence instance",
201
+ type=click.STRING,
202
+ )
203
+ @click.option(
204
+ "--confluence_url",
205
+ "-cu",
206
+ default="https://regscale.atlassian.net/wiki",
207
+ help="The base URL for the confluence instance",
208
+ type=click.STRING,
209
+ )
210
+ def update_docs(readme_token: str, confluence_user: str, confluence_token: str, confluence_url: str) -> None:
211
+ """Update the internal and external documentation for the CLI. This will update readme.io and confluence."""
212
+ from regscale.core.app.api import Api
213
+ from regscale.dev.docs import update_confluence, update_readme_io
214
+
215
+ api = Api()
216
+ if not readme_token:
217
+ api.logger.error("[red]No README_TOKEN environment variable found. Skipping readme.io updates.")
218
+ if not confluence_user or not confluence_token:
219
+ api.logger.error(
220
+ "[red]No CONFLUENCE_USER or CONFLUENCE_TOKEN environment variable found. Skipping confluence updates."
221
+ )
222
+
223
+ # walk through regscale.integrations folders and update the documentation
224
+ for root, _, files in os.walk("regscale/integrations"):
225
+ for file in files:
226
+ if file == "external.md" and readme_token:
227
+ api.auth = (readme_token, "")
228
+ update_readme_io(api, root, file)
229
+ elif file == "internal.md" and confluence_user and confluence_token:
230
+ api.auth = (confluence_user, confluence_token)
231
+ update_confluence(api, confluence_url, root, file)
232
+
233
+
234
+ if __name__ == "__main__":
235
+ cli()