contentctl 5.0.0a2__tar.gz → 5.0.0a3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/PKG-INFO +1 -1
  2. contentctl-5.0.0a3/contentctl/__init__.py +1 -0
  3. contentctl-5.0.0a3/contentctl/actions/build.py +136 -0
  4. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/deploy_acs.py +29 -24
  5. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/DetectionTestingManager.py +66 -41
  6. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/GitService.py +2 -4
  7. contentctl-5.0.0a3/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +83 -0
  8. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +163 -124
  9. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
  10. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/progress_bar.py +3 -0
  11. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/views/DetectionTestingView.py +15 -18
  12. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
  13. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
  14. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
  15. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/doc_gen.py +9 -5
  16. contentctl-5.0.0a3/contentctl/actions/initialize.py +75 -0
  17. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/inspect.py +118 -61
  18. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/new_content.py +78 -50
  19. contentctl-5.0.0a3/contentctl/actions/release_notes.py +370 -0
  20. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/reporting.py +23 -19
  21. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/test.py +31 -25
  22. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/validate.py +54 -34
  23. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/api.py +54 -45
  24. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/contentctl.py +10 -10
  25. contentctl-5.0.0a3/contentctl/enrichments/attack_enrichment.py +161 -0
  26. contentctl-5.0.0a3/contentctl/enrichments/cve_enrichment.py +70 -0
  27. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/enrichments/splunk_app_enrichment.py +38 -36
  28. contentctl-5.0.0a3/contentctl/helper/link_validator.py +195 -0
  29. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/helper/splunk_app.py +69 -41
  30. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/helper/utils.py +58 -39
  31. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/input/director.py +69 -37
  32. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/input/new_content_questions.py +26 -34
  33. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/input/yml_reader.py +22 -17
  34. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +250 -314
  35. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +58 -36
  36. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/alert_action.py +8 -8
  37. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/annotated_types.py +1 -1
  38. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/atomic.py +64 -54
  39. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/base_test.py +2 -1
  40. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/base_test_result.py +16 -8
  41. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/baseline.py +41 -30
  42. contentctl-5.0.0a3/contentctl/objects/baseline_tags.py +49 -0
  43. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/config.py +1 -1
  44. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/constants.py +29 -58
  45. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/correlation_search.py +75 -55
  46. contentctl-5.0.0a3/contentctl/objects/dashboard.py +114 -0
  47. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/data_source.py +13 -13
  48. contentctl-5.0.0a3/contentctl/objects/deployment.py +84 -0
  49. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/deployment_email.py +1 -1
  50. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/deployment_notable.py +2 -1
  51. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/deployment_phantom.py +5 -5
  52. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/deployment_rba.py +1 -1
  53. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/deployment_scheduling.py +1 -1
  54. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/deployment_slack.py +1 -1
  55. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/detection.py +5 -2
  56. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/detection_metadata.py +1 -0
  57. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/detection_stanza.py +7 -2
  58. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/detection_tags.py +54 -64
  59. contentctl-5.0.0a3/contentctl/objects/drilldown.py +102 -0
  60. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/enums.py +61 -43
  61. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/errors.py +16 -24
  62. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/integration_test.py +3 -3
  63. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/integration_test_result.py +1 -0
  64. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/investigation.py +41 -26
  65. contentctl-5.0.0a3/contentctl/objects/investigation_tags.py +45 -0
  66. contentctl-5.0.0a3/contentctl/objects/lookup.py +356 -0
  67. contentctl-5.0.0a3/contentctl/objects/macro.py +90 -0
  68. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/manual_test.py +3 -3
  69. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/manual_test_result.py +1 -0
  70. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/mitre_attack_enrichment.py +17 -16
  71. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/notable_action.py +2 -1
  72. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/notable_event.py +1 -3
  73. contentctl-5.0.0a3/contentctl/objects/playbook.py +68 -0
  74. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/playbook_tags.py +22 -16
  75. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/rba.py +14 -8
  76. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/risk_analysis_action.py +15 -11
  77. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/risk_event.py +27 -20
  78. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/risk_object.py +1 -0
  79. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/savedsearches_conf.py +9 -7
  80. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/security_content_object.py +5 -2
  81. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/story.py +45 -44
  82. contentctl-5.0.0a3/contentctl/objects/story_tags.py +66 -0
  83. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/test_group.py +5 -2
  84. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/threat_object.py +1 -0
  85. contentctl-5.0.0a3/contentctl/objects/throttling.py +55 -0
  86. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/unit_test.py +3 -4
  87. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/unit_test_baseline.py +4 -5
  88. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/unit_test_result.py +6 -6
  89. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/api_json_output.py +22 -22
  90. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/attack_nav_output.py +21 -21
  91. contentctl-5.0.0a3/contentctl/output/attack_nav_writer.py +67 -0
  92. contentctl-5.0.0a3/contentctl/output/conf_output.py +276 -0
  93. contentctl-5.0.0a3/contentctl/output/data_source_writer.py +52 -0
  94. contentctl-5.0.0a3/contentctl/output/doc_md_output.py +96 -0
  95. contentctl-5.0.0a3/contentctl/output/jinja_writer.py +37 -0
  96. contentctl-5.0.0a3/contentctl/output/json_writer.py +31 -0
  97. contentctl-5.0.0a3/contentctl/output/svg_output.py +73 -0
  98. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/transforms.j2 +2 -2
  99. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/yml_writer.py +18 -24
  100. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/pyproject.toml +1 -2
  101. contentctl-5.0.0a2/contentctl/__init__.py +0 -1
  102. contentctl-5.0.0a2/contentctl/actions/build.py +0 -103
  103. contentctl-5.0.0a2/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -65
  104. contentctl-5.0.0a2/contentctl/actions/initialize.py +0 -63
  105. contentctl-5.0.0a2/contentctl/actions/initialize_old.py +0 -245
  106. contentctl-5.0.0a2/contentctl/actions/release_notes.py +0 -240
  107. contentctl-5.0.0a2/contentctl/enrichments/attack_enrichment.py +0 -121
  108. contentctl-5.0.0a2/contentctl/enrichments/cve_enrichment.py +0 -64
  109. contentctl-5.0.0a2/contentctl/helper/link_validator.py +0 -172
  110. contentctl-5.0.0a2/contentctl/objects/baseline_tags.py +0 -42
  111. contentctl-5.0.0a2/contentctl/objects/dashboard.py +0 -100
  112. contentctl-5.0.0a2/contentctl/objects/deployment.py +0 -77
  113. contentctl-5.0.0a2/contentctl/objects/drilldown.py +0 -71
  114. contentctl-5.0.0a2/contentctl/objects/investigation_tags.py +0 -33
  115. contentctl-5.0.0a2/contentctl/objects/lookup.py +0 -235
  116. contentctl-5.0.0a2/contentctl/objects/macro.py +0 -73
  117. contentctl-5.0.0a2/contentctl/objects/observable.py +0 -39
  118. contentctl-5.0.0a2/contentctl/objects/playbook.py +0 -66
  119. contentctl-5.0.0a2/contentctl/objects/story_tags.py +0 -54
  120. contentctl-5.0.0a2/contentctl/objects/throttling.py +0 -46
  121. contentctl-5.0.0a2/contentctl/output/attack_nav_writer.py +0 -75
  122. contentctl-5.0.0a2/contentctl/output/conf_output.py +0 -220
  123. contentctl-5.0.0a2/contentctl/output/data_source_writer.py +0 -39
  124. contentctl-5.0.0a2/contentctl/output/doc_md_output.py +0 -70
  125. contentctl-5.0.0a2/contentctl/output/jinja_writer.py +0 -33
  126. contentctl-5.0.0a2/contentctl/output/json_writer.py +0 -19
  127. contentctl-5.0.0a2/contentctl/output/svg_output.py +0 -55
  128. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/LICENSE.md +0 -0
  129. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/README.md +0 -0
  130. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  131. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/helper/logger.py +0 -0
  132. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/objects/test_attack_data.py +0 -0
  133. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/conf_writer.py +0 -0
  134. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
  135. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  136. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
  137. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/app.conf.j2 +0 -0
  138. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/app.manifest.j2 +0 -0
  139. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/collections.j2 +0 -0
  140. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/content-version.j2 +0 -0
  141. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/detection_count.j2 +0 -0
  142. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/detection_coverage.j2 +0 -0
  143. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  144. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_detections.j2 +0 -0
  145. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_navigation.j2 +0 -0
  146. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  147. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  148. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  149. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_stories.j2 +0 -0
  150. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/doc_story_page.j2 +0 -0
  151. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
  152. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
  153. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/header.j2 +0 -0
  154. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/macros.j2 +0 -0
  155. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/panel.j2 +0 -0
  156. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
  157. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
  158. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
  159. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/server.conf.j2 +0 -0
  160. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/output/templates/workflow_actions.j2 +0 -0
  161. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/README.md +0 -0
  162. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_default.yml +0 -0
  163. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  164. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  165. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  166. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/README.md +0 -0
  167. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  168. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/default/commands.conf +0 -0
  169. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
  170. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  171. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  172. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  173. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  174. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/metadata/default.meta +0 -0
  175. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/static/appIcon.png +0 -0
  176. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  177. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  178. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  179. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
  180. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/datamodels_cim.conf +0 -0
  181. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/datamodels_custom.conf +0 -0
  182. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  183. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  184. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  185. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  186. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  187. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/detections/application/.gitkeep +0 -0
  188. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  189. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
  190. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/detections/network/.gitkeep +0 -0
  191. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/detections/web/.gitkeep +0 -0
  192. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  193. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
  194. {contentctl-5.0.0a2 → contentctl-5.0.0a3}/contentctl/templates/stories/cobalt_strike.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: contentctl
3
- Version: 5.0.0a2
3
+ Version: 5.0.0a3
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,136 @@
1
+ import shutil
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from contentctl.input.director import DirectorOutputDto
6
+ from contentctl.output.conf_output import ConfOutput
7
+ from contentctl.output.conf_writer import ConfWriter
8
+ from contentctl.output.api_json_output import ApiJsonOutput
9
+ from contentctl.output.data_source_writer import DataSourceWriter
10
+ from contentctl.objects.lookup import CSVLookup, Lookup_Type
11
+ import pathlib
12
+ import json
13
+ import datetime
14
+ import uuid
15
+
16
+ from contentctl.objects.config import build
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class BuildInputDto:
21
+ director_output_dto: DirectorOutputDto
22
+ config: build
23
+
24
+
25
+ class Build:
26
+ def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
27
+ if input_dto.config.build_app:
28
+ updated_conf_files: set[pathlib.Path] = set()
29
+ conf_output = ConfOutput(input_dto.config)
30
+
31
+ # Construct a path to a YML that does not actually exist.
32
+ # We mock this "fake" path since the YML does not exist.
33
+ # This ensures the checking for the existence of the CSV is correct
34
+ data_sources_fake_yml_path = (
35
+ input_dto.config.getPackageDirectoryPath()
36
+ / "lookups"
37
+ / "data_sources.yml"
38
+ )
39
+
40
+ # Construct a special lookup whose CSV is created at runtime and
41
+ # written directly into the lookups folder. We will delete this after a build,
42
+ # assuming that it is successful.
43
+ data_sources_lookup_csv_path = (
44
+ input_dto.config.getPackageDirectoryPath()
45
+ / "lookups"
46
+ / "data_sources.csv"
47
+ )
48
+
49
+ DataSourceWriter.writeDataSourceCsv(
50
+ input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path
51
+ )
52
+ input_dto.director_output_dto.addContentToDictMappings(
53
+ CSVLookup.model_construct(
54
+ name="data_sources",
55
+ id=uuid.UUID("b45c1403-6e09-47b0-824f-cf6e44f15ac8"),
56
+ version=1,
57
+ author=input_dto.config.app.author_name,
58
+ date=datetime.date.today(),
59
+ description="A lookup file that will contain the data source objects for detections.",
60
+ lookup_type=Lookup_Type.csv,
61
+ file_path=data_sources_fake_yml_path,
62
+ )
63
+ )
64
+ updated_conf_files.update(conf_output.writeHeaders())
65
+ updated_conf_files.update(
66
+ conf_output.writeLookups(input_dto.director_output_dto.lookups)
67
+ )
68
+ updated_conf_files.update(
69
+ conf_output.writeDetections(input_dto.director_output_dto.detections)
70
+ )
71
+ updated_conf_files.update(
72
+ conf_output.writeStories(input_dto.director_output_dto.stories)
73
+ )
74
+ updated_conf_files.update(
75
+ conf_output.writeBaselines(input_dto.director_output_dto.baselines)
76
+ )
77
+ updated_conf_files.update(
78
+ conf_output.writeInvestigations(
79
+ input_dto.director_output_dto.investigations
80
+ )
81
+ )
82
+ updated_conf_files.update(
83
+ conf_output.writeMacros(input_dto.director_output_dto.macros)
84
+ )
85
+ updated_conf_files.update(
86
+ conf_output.writeDashboards(input_dto.director_output_dto.dashboards)
87
+ )
88
+ updated_conf_files.update(conf_output.writeMiscellaneousAppFiles())
89
+
90
+ # Ensure that the conf file we just generated/update is syntactically valid
91
+ for conf_file in updated_conf_files:
92
+ ConfWriter.validateConfFile(conf_file)
93
+
94
+ conf_output.packageApp()
95
+
96
+ print(
97
+ f"Build of '{input_dto.config.app.title}' APP successful to {input_dto.config.getPackageFilePath()}"
98
+ )
99
+
100
+ if input_dto.config.build_api:
101
+ shutil.rmtree(input_dto.config.getAPIPath(), ignore_errors=True)
102
+ input_dto.config.getAPIPath().mkdir(parents=True)
103
+ api_json_output = ApiJsonOutput(
104
+ input_dto.config.getAPIPath(), input_dto.config.app.label
105
+ )
106
+ api_json_output.writeDetections(input_dto.director_output_dto.detections)
107
+ api_json_output.writeStories(input_dto.director_output_dto.stories)
108
+ api_json_output.writeBaselines(input_dto.director_output_dto.baselines)
109
+ api_json_output.writeInvestigations(
110
+ input_dto.director_output_dto.investigations
111
+ )
112
+ api_json_output.writeLookups(input_dto.director_output_dto.lookups)
113
+ api_json_output.writeMacros(input_dto.director_output_dto.macros)
114
+ api_json_output.writeDeployments(input_dto.director_output_dto.deployments)
115
+
116
+ # create version file for sse api
117
+ version_file = input_dto.config.getAPIPath() / "version.json"
118
+ utc_time = (
119
+ datetime.datetime.now(datetime.timezone.utc)
120
+ .replace(microsecond=0, tzinfo=None)
121
+ .isoformat()
122
+ )
123
+ version_dict = {
124
+ "version": {
125
+ "name": f"v{input_dto.config.app.version}",
126
+ "published_at": f"{utc_time}Z",
127
+ }
128
+ }
129
+ with open(version_file, "w") as version_f:
130
+ json.dump(version_dict, version_f)
131
+
132
+ print(
133
+ f"Build of '{input_dto.config.app.title}' API successful to {input_dto.config.getAPIPath()}"
134
+ )
135
+
136
+ return input_dto.director_output_dto
@@ -4,52 +4,57 @@ import pprint
4
4
 
5
5
 
6
6
  class Deploy:
7
- def execute(self, config: deploy_acs, appinspect_token:str) -> None:
8
-
9
- #The following common headers are used by both Clasic and Victoria
7
+ def execute(self, config: deploy_acs, appinspect_token: str) -> None:
8
+ # The following common headers are used by both Clasic and Victoria
10
9
  headers = {
11
- 'Authorization': f'Bearer {config.splunk_cloud_jwt_token}',
12
- 'ACS-Legal-Ack': 'Y'
10
+ "Authorization": f"Bearer {config.splunk_cloud_jwt_token}",
11
+ "ACS-Legal-Ack": "Y",
13
12
  }
14
13
  try:
15
-
16
- with open(config.getPackageFilePath(include_version=False),'rb') as app_data:
17
- #request_data = app_data.read()
14
+ with open(
15
+ config.getPackageFilePath(include_version=False), "rb"
16
+ ) as app_data:
17
+ # request_data = app_data.read()
18
18
  if config.stack_type == StackType.classic:
19
19
  # Classic instead uses a form to store token and package
20
20
  # https://docs.splunk.com/Documentation/SplunkCloud/9.1.2308/Config/ManageApps#Manage_private_apps_using_the_ACS_API_on_Classic_Experience
21
21
  address = f"https://admin.splunk.com/{config.splunk_cloud_stack}/adminconfig/v2/apps"
22
-
23
- form_data = {
24
- 'token': (None, appinspect_token),
25
- 'package': app_data
26
- }
27
- res = post(address, headers=headers, files = form_data)
22
+
23
+ form_data = {"token": (None, appinspect_token), "package": app_data}
24
+ res = post(address, headers=headers, files=form_data)
28
25
  elif config.stack_type == StackType.victoria:
29
26
  # Victoria uses the X-Splunk-Authorization Header
30
27
  # It also uses --data-binary for the app content
31
28
  # https://docs.splunk.com/Documentation/SplunkCloud/9.1.2308/Config/ManageApps#Manage_private_apps_using_the_ACS_API_on_Victoria_Experience
32
- headers.update({'X-Splunk-Authorization': appinspect_token})
29
+ headers.update({"X-Splunk-Authorization": appinspect_token})
33
30
  address = f"https://admin.splunk.com/{config.splunk_cloud_stack}/adminconfig/v2/apps/victoria"
34
31
  res = post(address, headers=headers, data=app_data.read())
35
32
  else:
36
33
  raise Exception(f"Unsupported stack type: '{config.stack_type}'")
37
34
  except Exception as e:
38
- raise Exception(f"Error installing to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS:\n{str(e)}")
39
-
35
+ raise Exception(
36
+ f"Error installing to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS:\n{str(e)}"
37
+ )
38
+
40
39
  try:
41
40
  # Request went through and completed, but may have returned a non-successful error code.
42
41
  # This likely includes a more verbose response describing the error
43
42
  res.raise_for_status()
44
43
  print(res.json())
45
- except Exception as e:
44
+ except Exception:
46
45
  try:
47
46
  error_text = res.json()
48
- except Exception as e:
47
+ except Exception:
49
48
  error_text = "No error text - request failed"
50
49
  formatted_error_text = pprint.pformat(error_text)
51
- print("While this may not be the cause of your error, ensure that the uid and appid of your Private App does not exist in Splunkbase\n"
52
- "ACS cannot deploy and app with the same uid or appid as one that exists in Splunkbase.")
53
- raise Exception(f"Error installing to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS:\n{formatted_error_text}")
54
-
55
- print(f"'{config.getPackageFilePath(include_version=False)}' successfully installed to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS!")
50
+ print(
51
+ "While this may not be the cause of your error, ensure that the uid and appid of your Private App does not exist in Splunkbase\n"
52
+ "ACS cannot deploy and app with the same uid or appid as one that exists in Splunkbase."
53
+ )
54
+ raise Exception(
55
+ f"Error installing to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS:\n{formatted_error_text}"
56
+ )
57
+
58
+ print(
59
+ f"'{config.getPackageFilePath(include_version=False)}' successfully installed to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS!"
60
+ )
@@ -1,25 +1,29 @@
1
- from typing import List,Union
2
- from contentctl.objects.config import test, test_servers, Container,Infrastructure
3
- from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import DetectionTestingInfrastructure
4
- from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureContainer import DetectionTestingInfrastructureContainer
5
- from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureServer import DetectionTestingInfrastructureServer
6
- from urllib.parse import urlparse
7
- from copy import deepcopy
1
+ from typing import List, Union
2
+ from contentctl.objects.config import test, test_servers, Container, Infrastructure
3
+ from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
4
+ DetectionTestingInfrastructure,
5
+ )
6
+ from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureContainer import (
7
+ DetectionTestingInfrastructureContainer,
8
+ )
9
+ from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureServer import (
10
+ DetectionTestingInfrastructureServer,
11
+ )
8
12
  import signal
9
13
  import datetime
14
+
10
15
  # from queue import Queue
11
16
  from dataclasses import dataclass
17
+
12
18
  # import threading
13
- import ctypes
14
19
  from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
15
- DetectionTestingInfrastructure,
16
20
  DetectionTestingManagerOutputDto,
17
21
  )
18
22
  from contentctl.actions.detection_testing.views.DetectionTestingView import (
19
23
  DetectionTestingView,
20
24
  )
21
25
  from contentctl.objects.enums import PostTestBehavior
22
- from pydantic import BaseModel, Field
26
+ from pydantic import BaseModel
23
27
  from contentctl.objects.detection import Detection
24
28
  import concurrent.futures
25
29
  import docker
@@ -27,7 +31,7 @@ import docker
27
31
 
28
32
  @dataclass(frozen=False)
29
33
  class DetectionTestingManagerInputDto:
30
- config: Union[test,test_servers]
34
+ config: Union[test, test_servers]
31
35
  detections: List[Detection]
32
36
  views: list[DetectionTestingView]
33
37
 
@@ -64,15 +68,18 @@ class DetectionTestingManager(BaseModel):
64
68
  print("*******************************")
65
69
 
66
70
  signal.signal(signal.SIGINT, sigint_handler)
67
-
68
- with concurrent.futures.ThreadPoolExecutor(
69
- max_workers=len(self.input_dto.config.test_instances),
70
- ) as instance_pool, concurrent.futures.ThreadPoolExecutor(
71
- max_workers=len(self.input_dto.views)
72
- ) as view_runner, concurrent.futures.ThreadPoolExecutor(
73
- max_workers=len(self.input_dto.config.test_instances),
74
- ) as view_shutdowner:
75
71
 
72
+ with (
73
+ concurrent.futures.ThreadPoolExecutor(
74
+ max_workers=len(self.input_dto.config.test_instances),
75
+ ) as instance_pool,
76
+ concurrent.futures.ThreadPoolExecutor(
77
+ max_workers=len(self.input_dto.views)
78
+ ) as view_runner,
79
+ concurrent.futures.ThreadPoolExecutor(
80
+ max_workers=len(self.input_dto.config.test_instances),
81
+ ) as view_shutdowner,
82
+ ):
76
83
  # Start all the views
77
84
  future_views = {
78
85
  view_runner.submit(view.setup): view for view in self.input_dto.views
@@ -86,7 +93,7 @@ class DetectionTestingManager(BaseModel):
86
93
  # Wait for all instances to be set up
87
94
  for future in concurrent.futures.as_completed(future_instances_setup):
88
95
  try:
89
- result = future.result()
96
+ future.result()
90
97
  except Exception as e:
91
98
  self.output_dto.terminate = True
92
99
  print(f"Error setting up container: {str(e)}")
@@ -101,7 +108,7 @@ class DetectionTestingManager(BaseModel):
101
108
  # Wait for execution to finish
102
109
  for future in concurrent.futures.as_completed(future_instances_execute):
103
110
  try:
104
- result = future.result()
111
+ future.result()
105
112
  except Exception as e:
106
113
  self.output_dto.terminate = True
107
114
  print(f"Error running in container: {str(e)}")
@@ -114,34 +121,43 @@ class DetectionTestingManager(BaseModel):
114
121
  }
115
122
  for future in concurrent.futures.as_completed(future_views_shutdowner):
116
123
  try:
117
- result = future.result()
124
+ future.result()
118
125
  except Exception as e:
119
126
  print(f"Error stopping view: {str(e)}")
120
127
 
121
128
  # Wait for original view-related threads to complete
122
129
  for future in concurrent.futures.as_completed(future_views):
123
130
  try:
124
- result = future.result()
131
+ future.result()
125
132
  except Exception as e:
126
133
  print(f"Error running container: {str(e)}")
127
134
 
128
135
  return self.output_dto
129
136
 
130
137
  def create_DetectionTestingInfrastructureObjects(self):
131
- #Make sure that, if we need to, we pull the appropriate container
138
+ # Make sure that, if we need to, we pull the appropriate container
132
139
  for infrastructure in self.input_dto.config.test_instances:
133
- if (isinstance(self.input_dto.config, test) and isinstance(infrastructure, Container)):
140
+ if isinstance(self.input_dto.config, test) and isinstance(
141
+ infrastructure, Container
142
+ ):
134
143
  try:
135
144
  client = docker.from_env()
136
- except Exception as e:
137
- raise Exception("Unable to connect to docker. Are you sure that docker is running on this host?")
145
+ except Exception:
146
+ raise Exception(
147
+ "Unable to connect to docker. Are you sure that docker is running on this host?"
148
+ )
138
149
  try:
139
-
140
- parts = self.input_dto.config.container_settings.full_image_path.split(':')
150
+ parts = (
151
+ self.input_dto.config.container_settings.full_image_path.split(
152
+ ":"
153
+ )
154
+ )
141
155
  if len(parts) != 2:
142
- raise Exception(f"Expected to find a name:tag in {self.input_dto.config.container_settings.full_image_path}, "
143
- f"but instead found {parts}. Note that this path MUST include the tag, which is separated by ':'")
144
-
156
+ raise Exception(
157
+ f"Expected to find a name:tag in {self.input_dto.config.container_settings.full_image_path}, "
158
+ f"but instead found {parts}. Note that this path MUST include the tag, which is separated by ':'"
159
+ )
160
+
145
161
  print(
146
162
  f"Getting the latest version of the container image [{self.input_dto.config.container_settings.full_image_path}]...",
147
163
  end="",
@@ -151,12 +167,15 @@ class DetectionTestingManager(BaseModel):
151
167
  print("done!")
152
168
  break
153
169
  except Exception as e:
154
- raise Exception(f"Failed to pull docker container image [{self.input_dto.config.container_settings.full_image_path}]: {str(e)}")
170
+ raise Exception(
171
+ f"Failed to pull docker container image [{self.input_dto.config.container_settings.full_image_path}]: {str(e)}"
172
+ )
155
173
 
156
174
  already_staged_container_files = False
157
175
  for infrastructure in self.input_dto.config.test_instances:
158
-
159
- if (isinstance(self.input_dto.config, test) and isinstance(infrastructure, Container)):
176
+ if isinstance(self.input_dto.config, test) and isinstance(
177
+ infrastructure, Container
178
+ ):
160
179
  # Stage the files in the apps dir so that they can be passed directly to
161
180
  # subsequent containers. Do this here, instead of inside each container, to
162
181
  # avoid duplicate downloads/moves/copies
@@ -166,18 +185,24 @@ class DetectionTestingManager(BaseModel):
166
185
 
167
186
  self.detectionTestingInfrastructureObjects.append(
168
187
  DetectionTestingInfrastructureContainer(
169
- global_config=self.input_dto.config, infrastructure=infrastructure, sync_obj=self.output_dto
188
+ global_config=self.input_dto.config,
189
+ infrastructure=infrastructure,
190
+ sync_obj=self.output_dto,
170
191
  )
171
192
  )
172
193
 
173
- elif (isinstance(self.input_dto.config, test_servers) and isinstance(infrastructure, Infrastructure)):
194
+ elif isinstance(self.input_dto.config, test_servers) and isinstance(
195
+ infrastructure, Infrastructure
196
+ ):
174
197
  self.detectionTestingInfrastructureObjects.append(
175
198
  DetectionTestingInfrastructureServer(
176
- global_config=self.input_dto.config, infrastructure=infrastructure, sync_obj=self.output_dto
199
+ global_config=self.input_dto.config,
200
+ infrastructure=infrastructure,
201
+ sync_obj=self.output_dto,
177
202
  )
178
203
  )
179
204
 
180
205
  else:
181
-
182
- raise Exception(f"Unsupported target infrastructure '{infrastructure}' and config type {self.input_dto.config}")
183
-
206
+ raise Exception(
207
+ f"Unsupported target infrastructure '{infrastructure}' and config type {self.input_dto.config}"
208
+ )
@@ -10,6 +10,7 @@ from pygit2.enums import DeltaStatus
10
10
  if TYPE_CHECKING:
11
11
  from contentctl.input.director import DirectorOutputDto
12
12
 
13
+ from contentctl.input.director import DirectorOutputDto
13
14
  from contentctl.objects.config import All, Changes, Selected, test_common
14
15
  from contentctl.objects.data_source import DataSource
15
16
  from contentctl.objects.detection import Detection
@@ -22,9 +23,6 @@ logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
22
23
  LOGGER = logging.getLogger(__name__)
23
24
 
24
25
 
25
- from contentctl.input.director import DirectorOutputDto
26
-
27
-
28
26
  class GitService(BaseModel):
29
27
  director: DirectorOutputDto
30
28
  config: test_common
@@ -79,7 +77,7 @@ class GitService(BaseModel):
79
77
  updated_datasources: set[DataSource] = set()
80
78
 
81
79
  for diff in all_diffs:
82
- if type(diff) == pygit2.Patch:
80
+ if type(diff) is pygit2.Patch:
83
81
  if diff.delta.status in (
84
82
  DeltaStatus.ADDED,
85
83
  DeltaStatus.MODIFIED,
@@ -0,0 +1,83 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+
5
+ RAW_BADGE_SVG = """<?xml version="1.0"?>
6
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="20">
7
+ <linearGradient id="a" x2="0" y2="100%">
8
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
9
+ <stop offset="2" stop-opacity=".1"/>
10
+ </linearGradient>
11
+
12
+ <rect rx="3" width="60" height="20" fill="#555"/> <!-- Comment -->
13
+ <rect rx="3" x="60" width="40" height="20" fill="#4c1"/>
14
+
15
+ <path fill="#4c1" d="M58 0h4v20h-4z"/>
16
+
17
+ <rect rx="3" width="100" height="20" fill="url(#a)"/>
18
+ <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
19
+ <text x="30" y="14">{}</text>
20
+ <text x="80" y="14">{}</text>
21
+ </g>
22
+ </svg>"""
23
+
24
+
25
+ parser = argparse.ArgumentParser(
26
+ description="Use a summary.json file to generate a test coverage badge"
27
+ )
28
+ parser.add_argument(
29
+ "-i",
30
+ "--input_summary_file",
31
+ type=argparse.FileType("r"),
32
+ required=True,
33
+ help="Summary file to use to generate the pass percentage badge",
34
+ )
35
+ parser.add_argument(
36
+ "-o",
37
+ "--output_badge_file",
38
+ type=argparse.FileType("w"),
39
+ required=True,
40
+ help="Name of the badge to output",
41
+ )
42
+ parser.add_argument(
43
+ "-s", "--badge_string", type=str, required=True, help="Name of the badge to output"
44
+ )
45
+
46
+
47
+ try:
48
+ results = parser.parse_args()
49
+ except Exception as e:
50
+ print(f"Error parsing arguments: {str(e)}")
51
+ exit(1)
52
+
53
+ try:
54
+ summary_info = json.loads(results.input_summary_file.read())
55
+ except Exception as e:
56
+ print(f"Error loading {results.input_summary_file.name} JSON file: {str(e)}")
57
+ sys.exit(1)
58
+
59
+ if "summary" not in summary_info:
60
+ print("Missing 'summary' key in {results.input_summary_file.name}")
61
+ sys.exit(1)
62
+ elif (
63
+ "PASS_RATE" not in summary_info["summary"]
64
+ or "TESTS_PASSED" not in summary_info["summary"]
65
+ ):
66
+ print(
67
+ f"Missing PASS_RATE in 'summary' section of {results.input_summary_file.name}"
68
+ )
69
+ sys.exit(1)
70
+ pass_percent = 100 * summary_info["summary"]["PASS_RATE"]
71
+
72
+
73
+ try:
74
+ results.output_badge_file.write(
75
+ RAW_BADGE_SVG.format(results.badge_string, "{:2.1f}%".format(pass_percent))
76
+ )
77
+ except Exception as e:
78
+ print(f"Error generating badge: {str(e)}")
79
+ sys.exit(1)
80
+
81
+
82
+ print(f"Badge {results.output_badge_file.name} successfully generated!")
83
+ sys.exit(0)