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,334 @@
1
+ import csv
2
+ import datetime
3
+ import uuid
4
+ from typing import Dict, List, Any, Optional
5
+
6
+ from regscale.core.app import create_logger
7
+ from regscale.core.app.utils.catalog_utils.common import parentheses_to_dot
8
+ from regscale.models import regscale_models as rm, ControlImplementationStatus
9
+
10
+ logger = create_logger()
11
+ # Define a list to hold log entries for CSV output
12
+ log_entries: List[Dict[str, str]] = []
13
+
14
+
15
+ def _log_change(action: str, model: str, message: str, changes: Optional[Dict[str, Any]] = None):
16
+ """
17
+ Log changes to both the logger and a list for later CSV output.
18
+ :param str action: The type of action (e.g., 'Add', 'Update', 'Remove')
19
+ :param str model: The model being changed (e.g., 'Control', 'Objective', 'Parameter')
20
+ :param str message: The message to log
21
+ :param Optional[Dict[str, Any]] changes: Optional changes to log
22
+ """
23
+ logger.info(message)
24
+ log_entries.append({"action": action, "model": model, "message": message, "changes": str(changes)})
25
+
26
+
27
+ def _write_log_to_csv(filename: str):
28
+ """
29
+ Write all log entries to a CSV file.
30
+
31
+ :param str filename: The filename for the CSV output
32
+ """
33
+ entry_count = len(log_entries)
34
+ if entry_count > 0:
35
+ logger.info(f"Writing {entry_count} changes to {filename}")
36
+ with open(filename, "w", newline="") as csvfile:
37
+ fieldnames = ["action", "model", "message", "changes"]
38
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
39
+ writer.writeheader()
40
+ for entry in log_entries:
41
+ writer.writerow(entry)
42
+ logger.info(f"Successfully wrote all entries to {filename}")
43
+ else:
44
+ logger.info("No changes to write to CSV")
45
+
46
+
47
+ def _log_summary():
48
+ """
49
+ Log statistics about the entries, summarized by action and model.
50
+ """
51
+ from collections import defaultdict
52
+ from rich.console import Console
53
+
54
+ console = Console()
55
+ summary: defaultdict[Any, int] = defaultdict(int)
56
+ for entry in log_entries:
57
+ key = (entry["action"], entry["model"])
58
+ summary[key] += 1
59
+
60
+ # Print statistics in a table-like format in green color
61
+ console.print("Summary of Changes:", style="green")
62
+ console.print(f"{'Action':<20} {'Model':<20} {'Count':<10}", style="green")
63
+ console.print("-" * 50, style="green")
64
+ for (action, model), count in summary.items():
65
+ console.print(f"{action:<20} {model:<20} {count:<10}", style="green")
66
+
67
+
68
+ def sync_controls(catalog_controls: Dict[str, rm.SecurityControl], ssp_id: int, dry_run: bool = False):
69
+ """
70
+ Sync controls from a catalog to an SSP
71
+
72
+ :param Dict[str, rm.SecurityControl] catalog_controls: List of controls from the catalog
73
+ :param int ssp_id: SSP ID
74
+ :param bool dry_run: Dry run flag
75
+ """
76
+ logger.info(f"Syncing controls from catalog to SSP: {ssp_id}")
77
+
78
+ catalog_id_to_label = {x.id: x.controlId for x in catalog_controls.values()}
79
+
80
+ ssp = rm.SecurityPlan.get_object(object_id=ssp_id)
81
+ if not ssp:
82
+ logger.warning("SSP not found")
83
+ exit(1)
84
+
85
+ ssp_controls = {
86
+ parentheses_to_dot(catalog_id_to_label[x.controlID]): x
87
+ for x in rm.ControlImplementation.get_all_by_parent(
88
+ parent_id=ssp.id, parent_module=rm.SecurityPlan.get_module_string()
89
+ )
90
+ if x.controlID in catalog_id_to_label
91
+ }
92
+
93
+ # Add or update controls
94
+ for control_id, control in catalog_controls.items():
95
+ sync_control(control, control_id, dry_run, ssp, ssp_controls)
96
+
97
+ # Remove controls not in the catalog
98
+ for ssp_control_id, ssp_control in ssp_controls.items():
99
+ if ssp_control.controlID not in catalog_id_to_label:
100
+ _log_change("Remove", "Control", f"Removing control {ssp_control.controlID} from SSP {ssp.systemName}")
101
+ if not dry_run:
102
+ ssp_control.delete()
103
+
104
+
105
+ def sync_control(
106
+ control: rm.Control,
107
+ control_id: str,
108
+ dry_run: bool,
109
+ ssp: rm.SecurityPlan,
110
+ ssp_controls: Dict[str, rm.ControlImplementation],
111
+ ):
112
+ """
113
+ Sync a control from a catalog to an SSP
114
+
115
+ :param rm.Control control: The control from the catalog
116
+ :param str control_id: The control ID
117
+ :param bool dry_run: Dry run flag
118
+ :param rm.SecurityPlan ssp: The SSP
119
+ :param Dict[str, rm.ControlImplementation] ssp_controls: The controls in the SSP
120
+ """
121
+ logger.debug(f"Syncing control {control_id}")
122
+ ssp_control = ssp_controls.get(parentheses_to_dot(control_id))
123
+ if ssp_control is None:
124
+ # Create a new control in the SSP
125
+ print(f"Control Owner ID: {ssp.createdById}")
126
+ print(f"API Handler User ID: {rm.ControlImplementation.get_user_id()}")
127
+ ssp_control = rm.ControlImplementation(
128
+ controlOwnerId=ssp.systemOwnerId or ssp.createdById or rm.ControlImplementation.get_user_id(),
129
+ status=ControlImplementationStatus.NotImplemented,
130
+ controlID=control.id,
131
+ parentId=ssp.id,
132
+ parentModule=ssp.get_module_string(),
133
+ )
134
+ _log_change(
135
+ "Add",
136
+ "Control",
137
+ f"Adding control {control.controlId} to SSP {ssp.systemName}",
138
+ changes=ssp_control.show_changes(),
139
+ )
140
+ if not dry_run:
141
+ ssp_control = ssp_control.create()
142
+ else:
143
+ # Update the existing control in the SSP
144
+ if ssp_control.controlOwnerId in ["", None]:
145
+ ssp_control.controlOwnerId = ssp.systemOwnerId or ssp.createdById or rm.ControlImplementation.get_user_id()
146
+ if ssp_control.has_changed():
147
+ _log_change(
148
+ "Update",
149
+ "Control",
150
+ f"Updating control {control.controlId} in SSP {ssp.systemName}",
151
+ changes=ssp_control.show_changes(),
152
+ )
153
+ if not dry_run:
154
+ ssp_control.save()
155
+ sync_objectives(control, dry_run, ssp_control)
156
+ sync_parameters(control, dry_run, ssp_control)
157
+
158
+
159
+ def sync_objectives(control: rm.SecurityControl, dry_run: bool, ssp_control: rm.Control):
160
+ """
161
+ Sync objectives for a control
162
+
163
+ :param rm.SecurityControl control: The control
164
+ :param bool dry_run: Dry run flag
165
+ :param rm.Control ssp_control: The SSP control
166
+ """
167
+ ssp_objectives: Dict[int, rm.ImplementationObjective] = { # type: ignore
168
+ x.objectiveId: x
169
+ for x in rm.ImplementationObjective.get_all_by_parent(
170
+ parent_id=ssp_control.id, parent_module=rm.ControlImplementation.get_module_string()
171
+ )
172
+ }
173
+ control_objectives: Dict[int, rm.ControlObjective] = { # type: ignore
174
+ x.id: x
175
+ for x in rm.ControlObjective.get_all_by_parent(
176
+ parent_id=control.id, parent_module=rm.SecurityControl.get_module_string()
177
+ )
178
+ }
179
+
180
+ # Remove objectives that are not in the catalog
181
+ for objective_id in ssp_objectives.keys():
182
+ if objective_id not in control_objectives.keys():
183
+ _log_change(
184
+ "Remove",
185
+ "Objective",
186
+ f"Removing objective {objective_id} from control {control.controlId}",
187
+ )
188
+ if not dry_run:
189
+ ssp_objectives[objective_id].delete()
190
+
191
+
192
+ def sync_parameters(control: rm.SecurityControl, dry_run: bool, ssp_control: rm.ControlImplementation):
193
+ """
194
+ Sync parameters for a control
195
+
196
+ :param rm.SecurityControl control: The control
197
+ :param bool dry_run: Dry run flag
198
+ :param rm.ControlImplementation ssp_control: The SSP control
199
+ """
200
+ ssp_parameters = get_ssp_parameters(ssp_control)
201
+ control_parameters = get_control_parameters(control)
202
+
203
+ # Add or update parameters
204
+ add_or_update_parameters(control_parameters, ssp_parameters, control, dry_run, ssp_control)
205
+
206
+ # Remove parameters that are not in the catalog
207
+ remove_unused_parameters(ssp_parameters, control_parameters, control, dry_run)
208
+
209
+
210
+ def get_ssp_parameters(ssp_control):
211
+ return {
212
+ x.parentParameterId: x
213
+ for x in rm.Parameter.get_all_by_parent(
214
+ parent_id=ssp_control.id, parent_module=rm.ControlImplementation.get_module_string()
215
+ )
216
+ }
217
+
218
+
219
+ def get_control_parameters(control):
220
+ return {
221
+ x.id: x
222
+ for x in rm.ControlParameter.get_all_by_parent(
223
+ parent_id=control.id, parent_module=rm.SecurityControl.get_module_string()
224
+ )
225
+ }
226
+
227
+
228
+ def add_or_update_parameters(control_parameters, ssp_parameters, control, dry_run, ssp_control):
229
+ for control_parameter in control_parameters.values():
230
+ if control_parameter.id in ssp_parameters:
231
+ update_parameter(ssp_parameters[control_parameter.id], control_parameter, control, dry_run)
232
+ else:
233
+ add_parameter(control_parameter, control, dry_run, ssp_control)
234
+
235
+
236
+ def update_parameter(ssp_parameter, control_parameter, control, dry_run):
237
+ ssp_parameter.parentParameterId = control_parameter.id
238
+ ssp_parameter.name = control_parameter.displayName
239
+ ssp_parameter.value = control_parameter.text
240
+ if ssp_parameter.has_changed():
241
+ _log_change(
242
+ "Update",
243
+ "Parameter",
244
+ f"Updating parameter {ssp_parameter.name} in control {control.controlId}",
245
+ changes=ssp_parameter.show_changes(),
246
+ )
247
+ if not dry_run:
248
+ ssp_parameter.save()
249
+
250
+
251
+ def add_parameter(control_parameter, control, dry_run, ssp_control):
252
+ ssp_parameter = rm.Parameter(
253
+ uuid=uuid.uuid4().__str__(),
254
+ name=control_parameter.displayName,
255
+ value=control_parameter.text or "",
256
+ controlImplementationId=ssp_control.id,
257
+ parentParameterId=control_parameter.id,
258
+ )
259
+ _log_change(
260
+ "Add",
261
+ "Parameter",
262
+ f"Adding parameter {ssp_parameter.name} to control {control.controlId}",
263
+ changes=ssp_parameter.show_changes(),
264
+ )
265
+ if not dry_run:
266
+ ssp_parameter = ssp_parameter.create()
267
+
268
+
269
+ def remove_unused_parameters(ssp_parameters, control_parameters, control, dry_run):
270
+ for parameter_id in ssp_parameters.keys():
271
+ if parameter_id not in control_parameters:
272
+ _log_change(
273
+ "Remove",
274
+ "Parameter",
275
+ f"Removing parameter {parameter_id} from control {control.controlId}",
276
+ )
277
+ if not dry_run:
278
+ ssp_parameters[parameter_id].delete()
279
+
280
+
281
+ def get_controls_from_profile(ssp_id: int) -> Dict[str, rm.SecurityControl]:
282
+ """
283
+ Get controls from a profile
284
+
285
+ :param int ssp_id: The SSP ID
286
+ :return: The controls
287
+ :rtype: Dict[str, rm.SecurityControl]
288
+ """
289
+ logger.info(f"Getting controls from profile: {ssp_id}")
290
+ profile_links = rm.ProfileLink.get_all_by_parent(
291
+ parent_id=ssp_id, parent_module=rm.SecurityPlan.get_module_string()
292
+ )
293
+ controls: List[rm.SecurityControl] = []
294
+ control_get_count = 0
295
+ for profile_link in profile_links:
296
+ profile_mappings: list[rm.ProfileMapping] = rm.ProfileMapping.get_all_by_parent(
297
+ parent_id=profile_link.profileId, parent_module=rm.Profile.get_module_string()
298
+ )
299
+ for profile_mapping in profile_mappings:
300
+ control = rm.SecurityControl.get_object(object_id=profile_mapping.controlID)
301
+ if control:
302
+ print(control.controlId, end=" ")
303
+ controls.append(control)
304
+ control_get_count += 1
305
+ if control_get_count % 10 == 0:
306
+ print()
307
+ else:
308
+ logger.warning(f"Control {profile_mapping.controlID} not found")
309
+ return {parentheses_to_dot(x.controlId): x for x in controls}
310
+
311
+
312
+ def sync_plan_controls(ssp_id: int, dry_run: bool = False):
313
+ """
314
+ Sync controls from a profile to an SSP
315
+
316
+ :param int ssp_id: The SSP ID
317
+ :param bool dry_run: Dry run flag
318
+ """
319
+ logger.info(f"Syncing controls from profile to SSP: {ssp_id}")
320
+ log_entries.clear()
321
+ catalog_controls = get_controls_from_profile(ssp_id=ssp_id)
322
+ sync_controls(catalog_controls=catalog_controls, ssp_id=ssp_id, dry_run=dry_run)
323
+ _write_log_to_csv(f"plan_{ssp_id}_log_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.csv")
324
+ _log_summary()
325
+
326
+
327
+ def sync_all_plans(dry_run: bool = False):
328
+ """
329
+ Sync all plans
330
+
331
+ :param bool dry_run: Dry run flag
332
+ """
333
+ for ssp in rm.SecurityPlan.get_list(): # type: rm.SecurityPlan
334
+ sync_plan_controls(ssp.id, dry_run)
@@ -0,0 +1,238 @@
1
+ """
2
+ Utility functions for working with files and folders.
3
+ """
4
+
5
+ import os
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Union, List, Iterator, Optional
9
+
10
+
11
+ class S3FileDownloadError(Exception):
12
+ """
13
+ Exception raised when an error occurs during S3 file downloadß
14
+ """
15
+
16
+ pass
17
+
18
+
19
+ def is_s3_path(path: Union[str, Path]) -> bool:
20
+ """
21
+ Check if the given path is an S3 URI.
22
+
23
+ :param Union[str, Path] path: The path to check
24
+ :return: True if the path is an S3 URI, False otherwise
25
+ :rtype: bool
26
+ """
27
+ return isinstance(path, str) and path.startswith("s3://")
28
+
29
+
30
+ def read_file(file_path: Union[str, Path]) -> str:
31
+ """
32
+ Read a file from local filesystem or S3.
33
+
34
+ :param Union[str, Path] file_path: Path to the file or S3 URI
35
+ :return: Content of the file
36
+ :rtype: str
37
+ """
38
+ import smart_open # type: ignore # Optimize import performance
39
+
40
+ with smart_open.open(str(file_path), "r") as f:
41
+ return f.read()
42
+
43
+
44
+ def find_files(path: Union[str, Path], pattern: str) -> List[Union[Path, str]]:
45
+ """
46
+ Find all files matching the pattern in the given path, including S3.
47
+
48
+ :param Union[str, Path] path: Path to a file, a folder, or an S3 URI
49
+ :param str pattern: File pattern to match (e.g., "*.nessus")
50
+ :return: List of Path objects for matching files or S3 URIs
51
+ :rtype: List[Union[Path, str]]
52
+ """
53
+ import boto3 # type: ignore # Optimize import performance
54
+
55
+ if is_s3_path(path):
56
+ s3_parts = path[5:].split("/", 1)
57
+ bucket = s3_parts[0]
58
+ prefix = s3_parts[1] if len(s3_parts) > 1 else ""
59
+
60
+ s3 = boto3.client("s3")
61
+ paginator = s3.get_paginator("list_objects_v2")
62
+
63
+ files: List[Union[Path, str]] = []
64
+ for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
65
+ for obj in page.get("Contents", []):
66
+ if obj["Key"].endswith(pattern.lstrip("*")):
67
+ files.append(f"s3://{bucket}/{obj['Key']}")
68
+ return files
69
+
70
+ file_path = Path(path)
71
+ if file_path.is_file():
72
+ return [file_path] if file_path.match(pattern) else []
73
+ return list(file_path.glob(pattern))
74
+
75
+
76
+ def move_file(src: Union[str, Path], dst: Union[str, Path]) -> None:
77
+ """
78
+ Move a file from src to dst. Works with local files and S3.
79
+
80
+ :param Union[str, Path] src: Source file path or S3 URI
81
+ :param Union[str, Path] dst: Destination file path or S3 URI
82
+ """
83
+ import smart_open # type: ignore # Optimize import performance
84
+
85
+ if is_s3_path(src):
86
+ import boto3 # type: ignore # Optimize import performance
87
+
88
+ # S3 to S3 move
89
+ if is_s3_path(dst):
90
+ s3 = boto3.client("s3")
91
+ src_parts = src[5:].split("/", 1)
92
+ dst_parts = dst[5:].split("/", 1)
93
+ s3.copy_object(
94
+ CopySource={"Bucket": src_parts[0], "Key": src_parts[1]}, Bucket=dst_parts[0], Key=dst_parts[1]
95
+ )
96
+ s3.delete_object(Bucket=src_parts[0], Key=src_parts[1])
97
+ else:
98
+ # S3 to local
99
+ with smart_open.open(src, "rb") as s_file, smart_open.open(dst, "wb") as d_file:
100
+ d_file.write(s_file.read())
101
+ s3 = boto3.client("s3")
102
+ src_parts = src[5:].split("/", 1)
103
+ s3.delete_object(Bucket=src_parts[0], Key=src_parts[1])
104
+ else:
105
+ # Local to local or local to S3
106
+ with smart_open.open(src, "rb") as s_file, smart_open.open(dst, "wb") as d_file:
107
+ d_file.write(s_file.read())
108
+ if not isinstance(dst, str) or not dst.startswith("s3://"):
109
+ os.remove(src)
110
+
111
+
112
+ def iterate_files(file_collection: List[Union[Path, str]]) -> Iterator[Union[Path, str]]:
113
+ """
114
+ Iterate over a collection of files, yielding each file path.
115
+
116
+ :param List[Union[Path, str]] file_collection: List of file paths or S3 URIs
117
+ :yield: Each file path or S3 URI
118
+ :rtype: Iterator[Union[Path, str]]
119
+ """
120
+ for file in file_collection:
121
+ yield file
122
+
123
+
124
+ def get_processed_file_path(file_path: Union[str, Path], processed_folder: str = "processed") -> Union[str, Path]:
125
+ """
126
+ Generate a path for the processed file, handling both local and S3 paths.
127
+
128
+ :param Union[str, Path] file_path: Original file path or S3 URI
129
+ :param str processed_folder: Name of the folder for processed files (default: "processed")
130
+ :return: Path or S3 URI for the processed file
131
+ :rtype: Union[str, Path]
132
+ """
133
+ if is_s3_path(file_path):
134
+ s3_parts = file_path[5:].split("/") # type: ignore # is_s3_path ensures string
135
+ bucket = s3_parts[0]
136
+ key = "/".join(s3_parts[1:])
137
+ new_key = f"processed/{os.path.basename(key)}"
138
+ return f"s3://{bucket}/{new_key}"
139
+ else:
140
+ file_path = Path(file_path)
141
+ timestamp = datetime.now().strftime("%Y%m%d-%I%M%S%p")
142
+ new_filename = f"{file_path.stem}_{timestamp}{file_path.suffix}".replace(" ", "_")
143
+ new_path = file_path.parent / processed_folder / new_filename
144
+ os.makedirs(new_path.parent, exist_ok=True)
145
+ return new_path
146
+
147
+
148
+ def get_files_by_folder(
149
+ import_folder: Union[str, Path],
150
+ exclude_non_scan_files: bool,
151
+ file_excludes: Optional[list[str]] = None,
152
+ directory_excludes: Optional[list[str]] = None,
153
+ ) -> List[str]:
154
+ """
155
+ Retrieves a list of file paths from a specified folder, excluding empty files or files that match the excludes list.
156
+
157
+ :param Union[str, Path] import_folder: The path to the folder from which to retrieve file paths.
158
+ :param bool exclude_non_scan_files: exclude files that are not scan files
159
+ :param Optional[List[str]] file_excludes: List of file extensions to exclude from the list of files
160
+ :param Optional[List[str]] directory_excludes: List of directories to exclude from the list of files
161
+ :return: A list of file paths for all non-empty files in the specified folder and subsequent subfolders.
162
+ :rtype: List[str]
163
+ """
164
+ if file_excludes is None:
165
+ file_excludes = []
166
+ if directory_excludes is None:
167
+ directory_excludes = []
168
+ file_path_list = []
169
+ if not os.path.isdir(import_folder):
170
+ from regscale.core.app.logz import create_logger
171
+
172
+ logger = create_logger()
173
+ logger.error(f"Folder '{import_folder}' does not exist.")
174
+ return file_path_list
175
+ for root, dir, files in os.walk(import_folder):
176
+ for file in files:
177
+ file_path = os.path.join(root, file)
178
+ if any(exclude_str in file_path for exclude_str in directory_excludes):
179
+ continue
180
+ if any(exclude_str in file for exclude_str in file_excludes) and exclude_non_scan_files:
181
+ continue
182
+ if os.path.getsize(file_path) == 0:
183
+ continue
184
+ file_path_list.append(os.path.join(root, file))
185
+ return file_path_list
186
+
187
+
188
+ def download_from_s3(bucket: str, prefix: str, local_path: Union[str, os.PathLike], aws_profile: str) -> None:
189
+ """
190
+ Downloads files from an S3 bucket to a local directory.
191
+
192
+ :param str bucket: Name of the S3 bucket
193
+ :param str prefix: Prefix (folder path) within the bucket
194
+ :param Union[str, PathLike] local_path: Local directory to download files to
195
+ :param str aws_profile: AWS profile to use for S3 access
196
+ :rtype: None
197
+ """
198
+ import boto3
199
+ import logging
200
+ from botocore.exceptions import ClientError
201
+
202
+ logger = logging.getLogger(__name__)
203
+
204
+ session = boto3.Session(profile_name=aws_profile)
205
+ s3_client = session.client("s3")
206
+
207
+ try:
208
+ # Create local directory if it doesn't exist
209
+ os.makedirs(local_path, exist_ok=True)
210
+
211
+ # List objects in bucket with given prefix
212
+ paginator = s3_client.get_paginator("list_objects_v2")
213
+ for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
214
+ if "Contents" not in page:
215
+ continue
216
+
217
+ for obj in page["Contents"]:
218
+ # Skip if object is a directory (ends with /)
219
+ if obj["Key"].endswith("/"):
220
+ continue
221
+
222
+ # Create the full local path, preserving directory structure
223
+ relative_path = obj["Key"]
224
+ if prefix:
225
+ # Remove the prefix from the key to get the relative path
226
+ relative_path = relative_path[len(prefix) :].lstrip("/")
227
+
228
+ local_file_path = os.path.join(local_path, relative_path)
229
+
230
+ # Create the directory structure if it doesn't exist
231
+ os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
232
+
233
+ logger.info(f"Downloading {obj['Key']} to {local_file_path}")
234
+ s3_client.download_file(bucket, obj["Key"], local_file_path)
235
+
236
+ except ClientError as e:
237
+ logger.error(f"Error downloading from S3: {str(e)}")
238
+ raise S3FileDownloadError(f"Failed to download files from S3: {str(e)}")
@@ -0,0 +1,81 @@
1
+ import datetime
2
+ import logging
3
+ from typing import Any, Optional
4
+
5
+ from regscale.core.utils.date import datetime_str, date_str
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def safe_float(value: Any, default: float = 0.0, field_name: str = "value") -> float:
11
+ """
12
+ Safely convert any value to a float.
13
+
14
+ :param Any value: The value to convert
15
+ :param float default: The default value to return if conversion fails
16
+ :param str field_name: The name of the field being parsed (for logging purposes)
17
+ :return: The parsed float value or the default value
18
+ :rtype: float
19
+ """
20
+ if value is None:
21
+ return default
22
+
23
+ try:
24
+ return float(value)
25
+ except (ValueError, TypeError):
26
+ logger.warning(f"Invalid float {field_name}: {value}. Defaulting to {default}")
27
+ return default
28
+
29
+
30
+ def safe_int(value: Any, default: int = 0, field_name: str = "value") -> int:
31
+ """
32
+ Safely convert any value to an integer.
33
+
34
+ :param Any value: The value to convert
35
+ :param int default: The default value to return if conversion fails
36
+ :param str field_name: The name of the field being parsed (for logging purposes)
37
+ :return: The parsed integer value or the default value
38
+ :rtype: int
39
+ """
40
+ if value is None:
41
+ return default
42
+
43
+ try:
44
+ return int(value)
45
+ except (ValueError, TypeError):
46
+ logger.warning(f"Invalid integer {field_name}: {value}. Defaulting to {default}")
47
+ return default
48
+
49
+
50
+ def safe_datetime_str(
51
+ value: Any, default: datetime.datetime = datetime.datetime.now(), date_format: Optional[str] = None
52
+ ) -> str:
53
+ """
54
+ Safely convert any value to a datetime.
55
+
56
+ :param Any value: The value to convert
57
+ :param datetime default: The default value to return if conversion fails
58
+ :param Optional[str] date_format: The date format to use for the datetime string, defaults to None
59
+ :return: The parsed datetime value or the default value
60
+ :rtype: str
61
+ """
62
+ value = datetime_str(value, date_format)
63
+ if not value:
64
+ value = datetime_str(default, date_format)
65
+ return value
66
+
67
+
68
+ def safe_date_str(value: Any, default: datetime.date = datetime.date.today()) -> str:
69
+ """
70
+ Safely convert any value to a date string.
71
+
72
+ :param Any value: The value to convert
73
+ :param date default: The default value to return if conversion fails
74
+ :return: The parsed date string value or the default value
75
+
76
+ :rtype: str
77
+ """
78
+ value = date_str(value)
79
+ if not value:
80
+ value = date_str(default)
81
+ return value