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.
- regscale/__init__.py +1 -0
- regscale/airflow/__init__.py +9 -0
- regscale/airflow/azure/__init__.py +9 -0
- regscale/airflow/azure/cli.py +89 -0
- regscale/airflow/azure/upload_dags.py +116 -0
- regscale/airflow/click_dags.py +127 -0
- regscale/airflow/click_mixins.py +82 -0
- regscale/airflow/config.py +25 -0
- regscale/airflow/factories/__init__.py +0 -0
- regscale/airflow/factories/connections.py +58 -0
- regscale/airflow/factories/workflows.py +78 -0
- regscale/airflow/hierarchy.py +88 -0
- regscale/airflow/operators/__init__.py +0 -0
- regscale/airflow/operators/click.py +36 -0
- regscale/airflow/sensors/__init__.py +0 -0
- regscale/airflow/sensors/sql.py +107 -0
- regscale/airflow/sessions/__init__.py +0 -0
- regscale/airflow/sessions/sql/__init__.py +3 -0
- regscale/airflow/sessions/sql/queries.py +64 -0
- regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
- regscale/airflow/tasks/__init__.py +0 -0
- regscale/airflow/tasks/branches.py +22 -0
- regscale/airflow/tasks/cli.py +116 -0
- regscale/airflow/tasks/click.py +73 -0
- regscale/airflow/tasks/debugging.py +9 -0
- regscale/airflow/tasks/groups.py +116 -0
- regscale/airflow/tasks/init.py +60 -0
- regscale/airflow/tasks/states.py +47 -0
- regscale/airflow/tasks/workflows.py +36 -0
- regscale/ansible/__init__.py +9 -0
- regscale/core/__init__.py +0 -0
- regscale/core/app/__init__.py +3 -0
- regscale/core/app/api.py +571 -0
- regscale/core/app/application.py +665 -0
- regscale/core/app/internal/__init__.py +136 -0
- regscale/core/app/internal/admin_actions.py +230 -0
- regscale/core/app/internal/assessments_editor.py +873 -0
- regscale/core/app/internal/catalog.py +316 -0
- regscale/core/app/internal/comparison.py +459 -0
- regscale/core/app/internal/control_editor.py +571 -0
- regscale/core/app/internal/encrypt.py +79 -0
- regscale/core/app/internal/evidence.py +1240 -0
- regscale/core/app/internal/file_uploads.py +151 -0
- regscale/core/app/internal/healthcheck.py +66 -0
- regscale/core/app/internal/login.py +305 -0
- regscale/core/app/internal/migrations.py +240 -0
- regscale/core/app/internal/model_editor.py +1701 -0
- regscale/core/app/internal/poam_editor.py +632 -0
- regscale/core/app/internal/workflow.py +105 -0
- regscale/core/app/logz.py +74 -0
- regscale/core/app/utils/XMLIR.py +258 -0
- regscale/core/app/utils/__init__.py +0 -0
- regscale/core/app/utils/api_handler.py +358 -0
- regscale/core/app/utils/app_utils.py +1110 -0
- regscale/core/app/utils/catalog_utils/__init__.py +0 -0
- regscale/core/app/utils/catalog_utils/common.py +91 -0
- regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
- regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
- regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
- regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
- regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
- regscale/core/app/utils/file_utils.py +238 -0
- regscale/core/app/utils/parser_utils.py +81 -0
- regscale/core/app/utils/pickle_file_handler.py +57 -0
- regscale/core/app/utils/regscale_utils.py +319 -0
- regscale/core/app/utils/report_utils.py +119 -0
- regscale/core/app/utils/variables.py +226 -0
- regscale/core/decorators.py +31 -0
- regscale/core/lazy_group.py +65 -0
- regscale/core/login.py +63 -0
- regscale/core/server/__init__.py +0 -0
- regscale/core/server/flask_api.py +473 -0
- regscale/core/server/helpers.py +373 -0
- regscale/core/server/rest.py +64 -0
- regscale/core/server/static/css/bootstrap.css +6030 -0
- regscale/core/server/static/css/bootstrap.min.css +6 -0
- regscale/core/server/static/css/main.css +176 -0
- regscale/core/server/static/images/regscale-cli.svg +49 -0
- regscale/core/server/static/images/regscale.svg +38 -0
- regscale/core/server/templates/base.html +74 -0
- regscale/core/server/templates/index.html +43 -0
- regscale/core/server/templates/login.html +28 -0
- regscale/core/server/templates/make_base64.html +22 -0
- regscale/core/server/templates/upload_STIG.html +109 -0
- regscale/core/server/templates/upload_STIG_result.html +26 -0
- regscale/core/server/templates/upload_ssp.html +144 -0
- regscale/core/server/templates/upload_ssp_result.html +128 -0
- regscale/core/static/__init__.py +0 -0
- regscale/core/static/regex.py +14 -0
- regscale/core/utils/__init__.py +117 -0
- regscale/core/utils/click_utils.py +13 -0
- regscale/core/utils/date.py +238 -0
- regscale/core/utils/graphql.py +254 -0
- regscale/core/utils/urls.py +23 -0
- regscale/dev/__init__.py +6 -0
- regscale/dev/analysis.py +454 -0
- regscale/dev/cli.py +235 -0
- regscale/dev/code_gen.py +492 -0
- regscale/dev/dirs.py +69 -0
- regscale/dev/docs.py +384 -0
- regscale/dev/monitoring.py +26 -0
- regscale/dev/profiling.py +216 -0
- regscale/exceptions/__init__.py +4 -0
- regscale/exceptions/license_exception.py +7 -0
- regscale/exceptions/validation_exception.py +9 -0
- regscale/integrations/__init__.py +1 -0
- regscale/integrations/commercial/__init__.py +486 -0
- regscale/integrations/commercial/ad.py +433 -0
- regscale/integrations/commercial/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/common.py +106 -0
- regscale/integrations/commercial/aqua/__init__.py +0 -0
- regscale/integrations/commercial/aqua/aqua.py +91 -0
- regscale/integrations/commercial/aws/__init__.py +6 -0
- regscale/integrations/commercial/aws/cli.py +322 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
- regscale/integrations/commercial/aws/inventory/base.py +64 -0
- regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
- regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
- regscale/integrations/commercial/aws/scanner.py +823 -0
- regscale/integrations/commercial/azure/__init__.py +0 -0
- regscale/integrations/commercial/azure/common.py +32 -0
- regscale/integrations/commercial/azure/intune.py +488 -0
- regscale/integrations/commercial/azure/scanner.py +49 -0
- regscale/integrations/commercial/burp.py +78 -0
- regscale/integrations/commercial/cpe.py +144 -0
- regscale/integrations/commercial/crowdstrike.py +1117 -0
- regscale/integrations/commercial/defender.py +1511 -0
- regscale/integrations/commercial/dependabot.py +210 -0
- regscale/integrations/commercial/durosuite/__init__.py +0 -0
- regscale/integrations/commercial/durosuite/api.py +1546 -0
- regscale/integrations/commercial/durosuite/process_devices.py +101 -0
- regscale/integrations/commercial/durosuite/scanner.py +637 -0
- regscale/integrations/commercial/durosuite/variables.py +21 -0
- regscale/integrations/commercial/ecr.py +90 -0
- regscale/integrations/commercial/gcp/__init__.py +237 -0
- regscale/integrations/commercial/gcp/auth.py +96 -0
- regscale/integrations/commercial/gcp/control_tests.py +238 -0
- regscale/integrations/commercial/gcp/variables.py +18 -0
- regscale/integrations/commercial/gitlab.py +332 -0
- regscale/integrations/commercial/grype.py +165 -0
- regscale/integrations/commercial/ibm.py +90 -0
- regscale/integrations/commercial/import_all/__init__.py +0 -0
- regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
- regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
- regscale/integrations/commercial/jira.py +1046 -0
- regscale/integrations/commercial/mappings/__init__.py +0 -0
- regscale/integrations/commercial/mappings/csf_controls.json +713 -0
- regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
- regscale/integrations/commercial/nessus/__init__.py +0 -0
- regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
- regscale/integrations/commercial/nessus/scanner.py +416 -0
- regscale/integrations/commercial/nexpose.py +90 -0
- regscale/integrations/commercial/okta.py +798 -0
- regscale/integrations/commercial/opentext/__init__.py +0 -0
- regscale/integrations/commercial/opentext/click.py +99 -0
- regscale/integrations/commercial/opentext/scanner.py +143 -0
- regscale/integrations/commercial/prisma.py +91 -0
- regscale/integrations/commercial/qualys.py +1462 -0
- regscale/integrations/commercial/salesforce.py +980 -0
- regscale/integrations/commercial/sap/__init__.py +0 -0
- regscale/integrations/commercial/sap/click.py +31 -0
- regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
- regscale/integrations/commercial/sap/sysdig/click.py +57 -0
- regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
- regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
- regscale/integrations/commercial/sap/tenable/click.py +49 -0
- regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
- regscale/integrations/commercial/servicenow.py +1756 -0
- regscale/integrations/commercial/sicura/__init__.py +0 -0
- regscale/integrations/commercial/sicura/api.py +855 -0
- regscale/integrations/commercial/sicura/commands.py +73 -0
- regscale/integrations/commercial/sicura/scanner.py +481 -0
- regscale/integrations/commercial/sicura/variables.py +16 -0
- regscale/integrations/commercial/snyk.py +90 -0
- regscale/integrations/commercial/sonarcloud.py +260 -0
- regscale/integrations/commercial/sqlserver.py +369 -0
- regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
- regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
- regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
- regscale/integrations/commercial/stigv2/__init__.py +0 -0
- regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
- regscale/integrations/commercial/stigv2/click_commands.py +95 -0
- regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
- regscale/integrations/commercial/synqly/__init__.py +0 -0
- regscale/integrations/commercial/synqly/assets.py +46 -0
- regscale/integrations/commercial/synqly/ticketing.py +132 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
- regscale/integrations/commercial/synqly_jira.py +840 -0
- regscale/integrations/commercial/tenablev2/__init__.py +0 -0
- regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
- regscale/integrations/commercial/tenablev2/click.py +1584 -0
- regscale/integrations/commercial/tenablev2/scanner.py +504 -0
- regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
- regscale/integrations/commercial/tenablev2/utils.py +78 -0
- regscale/integrations/commercial/tenablev2/variables.py +17 -0
- regscale/integrations/commercial/trivy.py +162 -0
- regscale/integrations/commercial/veracode.py +96 -0
- regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
- regscale/integrations/commercial/wizv2/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/click.py +429 -0
- regscale/integrations/commercial/wizv2/constants.py +1001 -0
- regscale/integrations/commercial/wizv2/issue.py +361 -0
- regscale/integrations/commercial/wizv2/models.py +112 -0
- regscale/integrations/commercial/wizv2/parsers.py +339 -0
- regscale/integrations/commercial/wizv2/sbom.py +115 -0
- regscale/integrations/commercial/wizv2/scanner.py +416 -0
- regscale/integrations/commercial/wizv2/utils.py +796 -0
- regscale/integrations/commercial/wizv2/variables.py +39 -0
- regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
- regscale/integrations/commercial/xray.py +91 -0
- regscale/integrations/integration/__init__.py +2 -0
- regscale/integrations/integration/integration.py +26 -0
- regscale/integrations/integration/inventory.py +17 -0
- regscale/integrations/integration/issue.py +100 -0
- regscale/integrations/integration_override.py +149 -0
- regscale/integrations/public/__init__.py +103 -0
- regscale/integrations/public/cisa.py +641 -0
- regscale/integrations/public/criticality_updater.py +70 -0
- regscale/integrations/public/emass.py +411 -0
- regscale/integrations/public/emass_slcm_import.py +697 -0
- regscale/integrations/public/fedramp/__init__.py +0 -0
- regscale/integrations/public/fedramp/appendix_parser.py +548 -0
- regscale/integrations/public/fedramp/click.py +479 -0
- regscale/integrations/public/fedramp/components.py +714 -0
- regscale/integrations/public/fedramp/docx_parser.py +259 -0
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
- regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
- regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
- regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
- regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
- regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
- regscale/integrations/public/fedramp/import_workbook.py +495 -0
- regscale/integrations/public/fedramp/inventory_items.py +244 -0
- regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
- regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
- regscale/integrations/public/fedramp/mappings/user.py +175 -0
- regscale/integrations/public/fedramp/mappings/values.py +141 -0
- regscale/integrations/public/fedramp/markdown_parser.py +150 -0
- regscale/integrations/public/fedramp/metadata.py +689 -0
- regscale/integrations/public/fedramp/models/__init__.py +59 -0
- regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
- regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
- regscale/integrations/public/fedramp/parts_mapper.py +107 -0
- regscale/integrations/public/fedramp/poam/__init__.py +0 -0
- regscale/integrations/public/fedramp/poam/scanner.py +851 -0
- regscale/integrations/public/fedramp/properties.py +201 -0
- regscale/integrations/public/fedramp/reporting.py +84 -0
- regscale/integrations/public/fedramp/resources.py +496 -0
- regscale/integrations/public/fedramp/rosetta.py +110 -0
- regscale/integrations/public/fedramp/ssp_logger.py +87 -0
- regscale/integrations/public/fedramp/system_characteristics.py +922 -0
- regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
- regscale/integrations/public/fedramp/system_implementation.py +190 -0
- regscale/integrations/public/fedramp/xml_utils.py +87 -0
- regscale/integrations/public/nist_catalog.py +275 -0
- regscale/integrations/public/oscal.py +1946 -0
- regscale/integrations/public/otx.py +169 -0
- regscale/integrations/scanner_integration.py +2692 -0
- regscale/integrations/variables.py +25 -0
- regscale/models/__init__.py +7 -0
- regscale/models/app_models/__init__.py +5 -0
- regscale/models/app_models/catalog_compare.py +213 -0
- regscale/models/app_models/click.py +252 -0
- regscale/models/app_models/datetime_encoder.py +21 -0
- regscale/models/app_models/import_validater.py +321 -0
- regscale/models/app_models/mapping.py +260 -0
- regscale/models/app_models/pipeline.py +37 -0
- regscale/models/click_models.py +413 -0
- regscale/models/config.py +154 -0
- regscale/models/email_style.css +67 -0
- regscale/models/hierarchy.py +8 -0
- regscale/models/inspect_models.py +79 -0
- regscale/models/integration_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/inspector.py +262 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
- regscale/models/integration_models/aqua.py +247 -0
- regscale/models/integration_models/azure_alerts.py +255 -0
- regscale/models/integration_models/base64.py +23 -0
- regscale/models/integration_models/burp.py +433 -0
- regscale/models/integration_models/burp_models.py +128 -0
- regscale/models/integration_models/cisa_kev_data.json +19333 -0
- regscale/models/integration_models/defender_data.py +93 -0
- regscale/models/integration_models/defenderimport.py +143 -0
- regscale/models/integration_models/drf.py +443 -0
- regscale/models/integration_models/ecr_models/__init__.py +0 -0
- regscale/models/integration_models/ecr_models/data.py +69 -0
- regscale/models/integration_models/ecr_models/ecr.py +239 -0
- regscale/models/integration_models/flat_file_importer.py +1079 -0
- regscale/models/integration_models/grype_import.py +247 -0
- regscale/models/integration_models/ibm.py +126 -0
- regscale/models/integration_models/implementation_results.py +85 -0
- regscale/models/integration_models/nexpose.py +140 -0
- regscale/models/integration_models/prisma.py +202 -0
- regscale/models/integration_models/qualys.py +720 -0
- regscale/models/integration_models/qualys_scanner.py +160 -0
- regscale/models/integration_models/sbom/__init__.py +0 -0
- regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
- regscale/models/integration_models/send_reminders.py +620 -0
- regscale/models/integration_models/snyk.py +155 -0
- regscale/models/integration_models/synqly_models/__init__.py +0 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -0
- regscale/models/integration_models/synqly_models/connector_types.py +22 -0
- regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
- regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
- regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
- regscale/models/integration_models/synqly_models/param.py +72 -0
- regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
- regscale/models/integration_models/synqly_models/tenants.py +39 -0
- regscale/models/integration_models/tenable_models/__init__.py +0 -0
- regscale/models/integration_models/tenable_models/integration.py +187 -0
- regscale/models/integration_models/tenable_models/models.py +513 -0
- regscale/models/integration_models/trivy_import.py +231 -0
- regscale/models/integration_models/veracode.py +217 -0
- regscale/models/integration_models/xray.py +135 -0
- regscale/models/locking.py +100 -0
- regscale/models/platform.py +110 -0
- regscale/models/regscale_models/__init__.py +67 -0
- regscale/models/regscale_models/assessment.py +570 -0
- regscale/models/regscale_models/assessment_plan.py +52 -0
- regscale/models/regscale_models/asset.py +567 -0
- regscale/models/regscale_models/asset_mapping.py +190 -0
- regscale/models/regscale_models/case.py +42 -0
- regscale/models/regscale_models/catalog.py +261 -0
- regscale/models/regscale_models/cci.py +46 -0
- regscale/models/regscale_models/change.py +167 -0
- regscale/models/regscale_models/checklist.py +372 -0
- regscale/models/regscale_models/comment.py +49 -0
- regscale/models/regscale_models/compliance_settings.py +112 -0
- regscale/models/regscale_models/component.py +412 -0
- regscale/models/regscale_models/component_mapping.py +65 -0
- regscale/models/regscale_models/control.py +38 -0
- regscale/models/regscale_models/control_implementation.py +1128 -0
- regscale/models/regscale_models/control_objective.py +261 -0
- regscale/models/regscale_models/control_parameter.py +100 -0
- regscale/models/regscale_models/control_test.py +34 -0
- regscale/models/regscale_models/control_test_plan.py +75 -0
- regscale/models/regscale_models/control_test_result.py +52 -0
- regscale/models/regscale_models/custom_field.py +245 -0
- regscale/models/regscale_models/data.py +109 -0
- regscale/models/regscale_models/data_center.py +40 -0
- regscale/models/regscale_models/deviation.py +203 -0
- regscale/models/regscale_models/email.py +97 -0
- regscale/models/regscale_models/evidence.py +47 -0
- regscale/models/regscale_models/evidence_mapping.py +40 -0
- regscale/models/regscale_models/facility.py +59 -0
- regscale/models/regscale_models/file.py +382 -0
- regscale/models/regscale_models/filetag.py +37 -0
- regscale/models/regscale_models/form_field_value.py +94 -0
- regscale/models/regscale_models/group.py +169 -0
- regscale/models/regscale_models/implementation_objective.py +335 -0
- regscale/models/regscale_models/implementation_option.py +275 -0
- regscale/models/regscale_models/implementation_role.py +33 -0
- regscale/models/regscale_models/incident.py +177 -0
- regscale/models/regscale_models/interconnection.py +43 -0
- regscale/models/regscale_models/issue.py +1176 -0
- regscale/models/regscale_models/leveraged_authorization.py +125 -0
- regscale/models/regscale_models/line_of_inquiry.py +52 -0
- regscale/models/regscale_models/link.py +205 -0
- regscale/models/regscale_models/meta_data.py +64 -0
- regscale/models/regscale_models/mixins/__init__.py +0 -0
- regscale/models/regscale_models/mixins/parent_cache.py +124 -0
- regscale/models/regscale_models/module.py +224 -0
- regscale/models/regscale_models/modules.py +191 -0
- regscale/models/regscale_models/objective.py +14 -0
- regscale/models/regscale_models/parameter.py +87 -0
- regscale/models/regscale_models/ports_protocol.py +81 -0
- regscale/models/regscale_models/privacy.py +89 -0
- regscale/models/regscale_models/profile.py +50 -0
- regscale/models/regscale_models/profile_link.py +68 -0
- regscale/models/regscale_models/profile_mapping.py +124 -0
- regscale/models/regscale_models/project.py +63 -0
- regscale/models/regscale_models/property.py +278 -0
- regscale/models/regscale_models/question.py +85 -0
- regscale/models/regscale_models/questionnaire.py +87 -0
- regscale/models/regscale_models/questionnaire_instance.py +177 -0
- regscale/models/regscale_models/rbac.py +132 -0
- regscale/models/regscale_models/reference.py +86 -0
- regscale/models/regscale_models/regscale_model.py +1643 -0
- regscale/models/regscale_models/requirement.py +29 -0
- regscale/models/regscale_models/risk.py +274 -0
- regscale/models/regscale_models/sbom.py +54 -0
- regscale/models/regscale_models/scan_history.py +436 -0
- regscale/models/regscale_models/search.py +53 -0
- regscale/models/regscale_models/security_control.py +132 -0
- regscale/models/regscale_models/security_plan.py +204 -0
- regscale/models/regscale_models/software_inventory.py +159 -0
- regscale/models/regscale_models/stake_holder.py +64 -0
- regscale/models/regscale_models/stig.py +647 -0
- regscale/models/regscale_models/supply_chain.py +152 -0
- regscale/models/regscale_models/system_role.py +188 -0
- regscale/models/regscale_models/system_role_external_assignment.py +40 -0
- regscale/models/regscale_models/tag.py +37 -0
- regscale/models/regscale_models/tag_mapping.py +19 -0
- regscale/models/regscale_models/task.py +133 -0
- regscale/models/regscale_models/threat.py +196 -0
- regscale/models/regscale_models/user.py +175 -0
- regscale/models/regscale_models/user_group.py +55 -0
- regscale/models/regscale_models/vulnerability.py +242 -0
- regscale/models/regscale_models/vulnerability_mapping.py +162 -0
- regscale/models/regscale_models/workflow.py +55 -0
- regscale/models/regscale_models/workflow_action.py +34 -0
- regscale/models/regscale_models/workflow_instance.py +269 -0
- regscale/models/regscale_models/workflow_instance_step.py +114 -0
- regscale/models/regscale_models/workflow_template.py +58 -0
- regscale/models/regscale_models/workflow_template_step.py +45 -0
- regscale/regscale.py +815 -0
- regscale/utils/__init__.py +7 -0
- regscale/utils/b64conversion.py +14 -0
- regscale/utils/click_utils.py +118 -0
- regscale/utils/decorators.py +48 -0
- regscale/utils/dict_utils.py +59 -0
- regscale/utils/files.py +79 -0
- regscale/utils/fxns.py +30 -0
- regscale/utils/graphql_client.py +113 -0
- regscale/utils/lists.py +16 -0
- regscale/utils/numbers.py +12 -0
- regscale/utils/shell.py +148 -0
- regscale/utils/string.py +121 -0
- regscale/utils/synqly_utils.py +165 -0
- regscale/utils/threading/__init__.py +8 -0
- regscale/utils/threading/threadhandler.py +131 -0
- regscale/utils/threading/threadsafe_counter.py +47 -0
- regscale/utils/threading/threadsafe_dict.py +242 -0
- regscale/utils/threading/threadsafe_list.py +83 -0
- regscale/utils/version.py +104 -0
- regscale/validation/__init__.py +0 -0
- regscale/validation/address.py +37 -0
- regscale/validation/record.py +48 -0
- regscale/visualization/__init__.py +5 -0
- regscale/visualization/click.py +34 -0
- regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
- regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
- regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
- regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
- regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
- regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
- tests/fixtures/__init__.py +2 -0
- tests/fixtures/api.py +87 -0
- tests/fixtures/models.py +91 -0
- tests/fixtures/test_fixture.py +144 -0
- tests/mocks/__init__.py +0 -0
- tests/mocks/objects.py +3 -0
- tests/mocks/response.py +32 -0
- tests/mocks/xml.py +13 -0
- tests/regscale/__init__.py +0 -0
- tests/regscale/core/__init__.py +0 -0
- tests/regscale/core/test_api.py +232 -0
- tests/regscale/core/test_app.py +406 -0
- tests/regscale/core/test_login.py +37 -0
- tests/regscale/core/test_logz.py +66 -0
- tests/regscale/core/test_sbom_generator.py +87 -0
- tests/regscale/core/test_validation_utils.py +163 -0
- tests/regscale/core/test_version.py +78 -0
- tests/regscale/models/__init__.py +0 -0
- tests/regscale/models/test_asset.py +71 -0
- tests/regscale/models/test_config.py +26 -0
- tests/regscale/models/test_control_implementation.py +27 -0
- tests/regscale/models/test_import.py +97 -0
- tests/regscale/models/test_issue.py +36 -0
- tests/regscale/models/test_mapping.py +52 -0
- tests/regscale/models/test_platform.py +31 -0
- tests/regscale/models/test_regscale_model.py +346 -0
- tests/regscale/models/test_report.py +32 -0
- tests/regscale/models/test_tenable_integrations.py +118 -0
- tests/regscale/models/test_user_model.py +121 -0
- tests/regscale/test_about.py +19 -0
- tests/regscale/test_authorization.py +65 -0
regscale/dev/analysis.py
ADDED
|
@@ -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()
|