howler-api 3.4.0.dev912__tar.gz → 3.4.0.dev927__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 (202) hide show
  1. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/PKG-INFO +1 -1
  2. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/search.py +0 -29
  3. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/collection.py +200 -17
  4. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/howler_store.py +2 -1
  5. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/store.py +12 -3
  6. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/config.py +45 -0
  7. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/pyproject.toml +1 -1
  8. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/README.md +0 -0
  9. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/__init__.py +0 -0
  10. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/__init__.py +0 -0
  11. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/add_label.py +0 -0
  12. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/add_to_bundle.py +0 -0
  13. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/change_field.py +0 -0
  14. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/demote.py +0 -0
  15. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/example_plugin.py +0 -0
  16. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/prioritization.py +0 -0
  17. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/promote.py +0 -0
  18. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/remove_from_bundle.py +0 -0
  19. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/remove_label.py +0 -0
  20. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/actions/transition.py +0 -0
  21. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/__init__.py +0 -0
  22. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/base.py +0 -0
  23. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/socket.py +0 -0
  24. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/__init__.py +0 -0
  25. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/action.py +0 -0
  26. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/analytic.py +0 -0
  27. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/auth.py +0 -0
  28. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/clue.py +0 -0
  29. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/configs.py +0 -0
  30. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/dossier.py +0 -0
  31. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/help.py +0 -0
  32. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/hit.py +0 -0
  33. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/notebook.py +0 -0
  34. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/overview.py +0 -0
  35. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/template.py +0 -0
  36. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/tool.py +0 -0
  37. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/user.py +0 -0
  38. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/utils/__init__.py +0 -0
  39. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/utils/etag.py +0 -0
  40. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/api/v1/view.py +0 -0
  41. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/app.py +0 -0
  42. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/README.md +0 -0
  43. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/__init__.py +0 -0
  44. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/classification.py +0 -0
  45. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/classification.yml +0 -0
  46. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/exceptions.py +0 -0
  47. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/loader.py +0 -0
  48. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/logging/__init__.py +0 -0
  49. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/logging/audit.py +0 -0
  50. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/logging/format.py +0 -0
  51. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/net.py +0 -0
  52. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/net_static.py +0 -0
  53. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/random_user.py +0 -0
  54. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/common/swagger.py +0 -0
  55. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/config.py +0 -0
  56. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/cronjobs/__init__.py +0 -0
  57. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/cronjobs/retention.py +0 -0
  58. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/cronjobs/rules.py +0 -0
  59. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/cronjobs/view_cleanup.py +0 -0
  60. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/README.md +0 -0
  61. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/__init__.py +0 -0
  62. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/bulk.py +0 -0
  63. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/constants.py +0 -0
  64. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/exceptions.py +0 -0
  65. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/migrations/fix_process.py +0 -0
  66. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/operations.py +0 -0
  67. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/schemas.py +0 -0
  68. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/support/__init__.py +0 -0
  69. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/support/build.py +0 -0
  70. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/support/schemas.py +0 -0
  71. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/datastore/types.py +0 -0
  72. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/error.py +0 -0
  73. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/README.md +0 -0
  74. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/__init__.py +0 -0
  75. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/generate_mitre.py +0 -0
  76. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/generate_sigma_rules.py +0 -0
  77. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/generate_tlds.py +0 -0
  78. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/reindex_data.py +0 -0
  79. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/external/wipe_databases.py +0 -0
  80. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/gunicorn_config.py +0 -0
  81. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/healthz.py +0 -0
  82. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/__init__.py +0 -0
  83. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/azure.py +0 -0
  84. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/discover.py +0 -0
  85. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/hit.py +0 -0
  86. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/oauth.py +0 -0
  87. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/search.py +0 -0
  88. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/workflow.py +0 -0
  89. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/helper/ws.py +0 -0
  90. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/README.md +0 -0
  91. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/__init__.py +0 -0
  92. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/base.py +0 -0
  93. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/charter.txt +0 -0
  94. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/helper.py +0 -0
  95. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/howler_enum.py +0 -0
  96. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/__init__.py +0 -0
  97. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/action.py +0 -0
  98. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/analytic.py +0 -0
  99. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/assemblyline.py +0 -0
  100. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/aws.py +0 -0
  101. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/azure.py +0 -0
  102. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/cbs.py +0 -0
  103. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/clue.py +0 -0
  104. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/dossier.py +0 -0
  105. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/__init__.py +0 -0
  106. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/agent.py +0 -0
  107. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/autonomous_system.py +0 -0
  108. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/client.py +0 -0
  109. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/cloud.py +0 -0
  110. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/code_signature.py +0 -0
  111. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/container.py +0 -0
  112. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/dns.py +0 -0
  113. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/egress.py +0 -0
  114. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/elf.py +0 -0
  115. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/email.py +0 -0
  116. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/error.py +0 -0
  117. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/event.py +0 -0
  118. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/faas.py +0 -0
  119. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/file.py +0 -0
  120. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/geo.py +0 -0
  121. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/group.py +0 -0
  122. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/hash.py +0 -0
  123. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/host.py +0 -0
  124. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/http.py +0 -0
  125. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/ingress.py +0 -0
  126. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/interface.py +0 -0
  127. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/network.py +0 -0
  128. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/observer.py +0 -0
  129. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/organization.py +0 -0
  130. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/os.py +0 -0
  131. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/pe.py +0 -0
  132. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/process.py +0 -0
  133. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/registry.py +0 -0
  134. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/related.py +0 -0
  135. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/rule.py +0 -0
  136. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/server.py +0 -0
  137. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/threat.py +0 -0
  138. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/tls.py +0 -0
  139. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/url.py +0 -0
  140. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/user.py +0 -0
  141. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/user_agent.py +0 -0
  142. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/ecs/vulnerability.py +0 -0
  143. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/gcp.py +0 -0
  144. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/hit.py +0 -0
  145. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/howler_data.py +0 -0
  146. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/lead.py +0 -0
  147. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/localized_label.py +0 -0
  148. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/overview.py +0 -0
  149. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/pivot.py +0 -0
  150. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/template.py +0 -0
  151. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/user.py +0 -0
  152. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/models/view.py +0 -0
  153. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/random_data.py +0 -0
  154. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/odm/randomizer.py +0 -0
  155. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/patched.py +0 -0
  156. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/plugins/__init__.py +0 -0
  157. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/plugins/config.py +0 -0
  158. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/__init__.py +0 -0
  159. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/README.md +0 -0
  160. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/__init__.py +0 -0
  161. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/counters.py +0 -0
  162. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/events.py +0 -0
  163. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/hash.py +0 -0
  164. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/lock.py +0 -0
  165. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/queues/__init__.py +0 -0
  166. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/queues/comms.py +0 -0
  167. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/queues/multi.py +0 -0
  168. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/queues/named.py +0 -0
  169. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/queues/priority.py +0 -0
  170. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/set.py +0 -0
  171. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  172. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/security/__init__.py +0 -0
  173. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/security/socket.py +0 -0
  174. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/security/utils.py +0 -0
  175. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/__init__.py +0 -0
  176. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/action_service.py +0 -0
  177. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/analytic_service.py +0 -0
  178. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/auth_service.py +0 -0
  179. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/config_service.py +0 -0
  180. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/dossier_service.py +0 -0
  181. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/event_service.py +0 -0
  182. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/hit_service.py +0 -0
  183. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/jwt_service.py +0 -0
  184. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/lucene_service.py +0 -0
  185. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/notebook_service.py +0 -0
  186. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/overview_service.py +0 -0
  187. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/template_service.py +0 -0
  188. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/services/user_service.py +0 -0
  189. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/telemetry.py +0 -0
  190. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/__init__.py +0 -0
  191. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/annotations.py +0 -0
  192. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/chunk.py +0 -0
  193. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/compat.py +0 -0
  194. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/constants.py +0 -0
  195. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/dict_utils.py +0 -0
  196. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/isotime.py +0 -0
  197. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/list_utils.py +0 -0
  198. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/lucene.py +0 -0
  199. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/path.py +0 -0
  200. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/socket_utils.py +0 -0
  201. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/str_utils.py +0 -0
  202. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev927}/howler/utils/uid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howler-api
3
- Version: 3.4.0.dev912
3
+ Version: 3.4.0.dev927
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -78,7 +78,6 @@ def search(index, **kwargs):
78
78
  sort => How to sort the results (not available in deep paging)
79
79
  fl => List of fields to return
80
80
  timeout => Maximum execution time (ms)
81
- use_archive => Allow access to the datastore achive (Default: False)
82
81
  track_total_hits => Track the total number of query matches, instead of stopping at 10000 (Default: False)
83
82
  metadata => A list of additional features to be added to the result alongside the raw results
84
83
 
@@ -118,18 +117,9 @@ def search(index, **kwargs):
118
117
  "track_total_hits",
119
118
  ]
120
119
  multi_fields = ["filters", "metadata"]
121
- boolean_fields = ["use_archive"]
122
120
 
123
121
  params, req_data = generate_params(request, fields, multi_fields)
124
122
 
125
- params.update(
126
- {
127
- k: str(req_data.get(k, "false")).lower() in ["true", ""]
128
- for k in boolean_fields
129
- if req_data.get(k, None) is not None
130
- }
131
- )
132
-
133
123
  if has_access_control(index):
134
124
  params.update({"access_control": user["access_control"]})
135
125
 
@@ -350,18 +340,9 @@ def sigma_search(index, **kwargs):
350
340
  "track_total_hits",
351
341
  ]
352
342
  multi_fields = ["filters"]
353
- boolean_fields = ["use_archive"]
354
343
 
355
344
  params, req_data = generate_params(request, fields, multi_fields)
356
345
 
357
- params.update(
358
- {
359
- k: str(req_data.get(k, "false")).lower() in ["true", ""]
360
- for k in boolean_fields
361
- if req_data.get(k, None) is not None
362
- }
363
- )
364
-
365
346
  if has_access_control(index):
366
347
  params.update({"access_control": user["access_control"]})
367
348
 
@@ -520,7 +501,6 @@ def count(index, **kwargs):
520
501
  Optional Arguments:
521
502
  filters => List of additional filter queries limit the data
522
503
  timeout => Maximum execution time (ms)
523
- use_archive => Allow access to the datastore achive (Default: False)
524
504
 
525
505
  Data Block:
526
506
  # Note that the data block is for POST requests only!
@@ -543,15 +523,6 @@ def count(index, **kwargs):
543
523
 
544
524
  params, req_data = generate_params(request, [], [])
545
525
 
546
- boolean_fields = ["use_archive"]
547
- params.update(
548
- {
549
- k: str(req_data.get(k, "false")).lower() in ["true", ""]
550
- for k in boolean_fields
551
- if req_data.get(k, None) is not None
552
- }
553
- )
554
-
555
526
  if has_access_control(index):
556
527
  params.update({"access_control": user["access_control"]})
557
528
 
@@ -215,7 +215,7 @@ class ESCollection(Generic[ModelType]):
215
215
  IGNORE_ENSURE_COLLECTION: bool = False
216
216
  ENSURE_COLLECTION_WARNED: bool = False
217
217
 
218
- def __init__(self, datastore: ESStore, name, model_class=None, validate=True, max_attempts=10):
218
+ def __init__(self, datastore: ESStore, name, model_class=None, validate=True, max_attempts=10, ilm_config=None):
219
219
  self.replicas = int(
220
220
  environ.get(
221
221
  f"ELASTIC_{name.upper()}_REPLICAS",
@@ -227,6 +227,7 @@ class ESCollection(Generic[ModelType]):
227
227
 
228
228
  self.datastore = datastore
229
229
  self.name = f"{APP_NAME}-{name}"
230
+ self.ilm_config = ilm_config
230
231
  self.index_name = f"{self.name}_hot"
231
232
  self.model_class = model_class
232
233
  self.validate = validate
@@ -1394,7 +1395,7 @@ class ESCollection(Generic[ModelType]):
1394
1395
 
1395
1396
  return prune(source_data, fields, self.stored_fields, mapping_class=Mapping)
1396
1397
 
1397
- def _search(self, args=None, deep_paging_id=None, use_archive=False, track_total_hits=None):
1398
+ def _search(self, args=None, deep_paging_id=None, track_total_hits=None):
1398
1399
  if args is None:
1399
1400
  args = []
1400
1401
 
@@ -1551,7 +1552,6 @@ class ESCollection(Generic[ModelType]):
1551
1552
  filters: list[str] | str | None = None,
1552
1553
  access_control: typing.Any = None,
1553
1554
  deep_paging_id: str | None = None,
1554
- use_archive: bool = False,
1555
1555
  track_total_hits: bool = False,
1556
1556
  script_fields: list[str] = [],
1557
1557
  ) -> SearchResult[ModelType]: ...
@@ -1569,7 +1569,6 @@ class ESCollection(Generic[ModelType]):
1569
1569
  filters: list[str] | str | None = None,
1570
1570
  access_control: typing.Any = None,
1571
1571
  deep_paging_id: str | None = None,
1572
- use_archive: bool = False,
1573
1572
  track_total_hits: bool = False,
1574
1573
  script_fields: list[str] = [],
1575
1574
  ) -> SearchResult[dict[str, typing.Any]]: ...
@@ -1586,7 +1585,6 @@ class ESCollection(Generic[ModelType]):
1586
1585
  filters=None,
1587
1586
  access_control=None,
1588
1587
  deep_paging_id=None,
1589
- use_archive=False,
1590
1588
  track_total_hits=None,
1591
1589
  script_fields=[],
1592
1590
  ):
@@ -1607,7 +1605,6 @@ class ESCollection(Generic[ModelType]):
1607
1605
 
1608
1606
  :param script_fields: List of name/script tuple of fields to be evaluated at runtime
1609
1607
  :param track_total_hits: Return to total matching document count
1610
- :param use_archive: Query also the archive
1611
1608
  :param deep_paging_id: ID of the next page during deep paging searches
1612
1609
  :param as_obj: Return objects instead of dictionaries
1613
1610
  :param query: lucene query to search for
@@ -1663,7 +1660,6 @@ class ESCollection(Generic[ModelType]):
1663
1660
  result = self._search(
1664
1661
  args,
1665
1662
  deep_paging_id=deep_paging_id,
1666
- use_archive=use_archive,
1667
1663
  track_total_hits=track_total_hits,
1668
1664
  )
1669
1665
 
@@ -1710,7 +1706,6 @@ class ESCollection(Generic[ModelType]):
1710
1706
  access_control=None,
1711
1707
  item_buffer_size=200,
1712
1708
  as_obj=True,
1713
- use_archive=False,
1714
1709
  ):
1715
1710
  """This function should perform a search through the datastore and stream
1716
1711
  all related results as a dictionary of key value pair where each keys
@@ -1723,7 +1718,6 @@ class ESCollection(Generic[ModelType]):
1723
1718
  >>> fl[x]: value
1724
1719
  >>> }
1725
1720
 
1726
- :param use_archive: Query also the archive
1727
1721
  :param as_obj: Return objects instead of dictionaries
1728
1722
  :param query: lucene query to search for
1729
1723
  :param fl: list of fields to return from the search
@@ -1947,7 +1941,6 @@ class ESCollection(Generic[ModelType]):
1947
1941
  mincount=None,
1948
1942
  filters=None,
1949
1943
  access_control=None,
1950
- use_archive=False,
1951
1944
  ):
1952
1945
  type_modifier = self._validate_steps_count(start, end, gap)
1953
1946
  start = type_modifier(start)
@@ -1986,7 +1979,7 @@ class ESCollection(Generic[ModelType]):
1986
1979
  if filters:
1987
1980
  args.append(("filters", filters))
1988
1981
 
1989
- result = self._search(args, use_archive=use_archive)
1982
+ result = self._search(args)
1990
1983
 
1991
1984
  # Convert the histogram into a dictionary
1992
1985
  return {
@@ -2006,7 +1999,6 @@ class ESCollection(Generic[ModelType]):
2006
1999
  mincount=None,
2007
2000
  filters=None,
2008
2001
  access_control=None,
2009
- use_archive=False,
2010
2002
  field_script=None,
2011
2003
  ):
2012
2004
  if not query:
@@ -2039,7 +2031,7 @@ class ESCollection(Generic[ModelType]):
2039
2031
  if field_script:
2040
2032
  args.append(("field_script", field_script))
2041
2033
 
2042
- result = self._search(args, use_archive=use_archive)
2034
+ result = self._search(args)
2043
2035
 
2044
2036
  # Convert the histogram into a dictionary
2045
2037
  return {
@@ -2052,7 +2044,6 @@ class ESCollection(Generic[ModelType]):
2052
2044
  query="id:*",
2053
2045
  filters=None,
2054
2046
  access_control=None,
2055
- use_archive=False,
2056
2047
  field_script=None,
2057
2048
  ):
2058
2049
  if filters is None:
@@ -2076,7 +2067,7 @@ class ESCollection(Generic[ModelType]):
2076
2067
  if field_script:
2077
2068
  args.append(("field_script", field_script))
2078
2069
 
2079
- result = self._search(args, use_archive=use_archive)
2070
+ result = self._search(args)
2080
2071
  return result["aggregations"][f"{field}_stats"]
2081
2072
 
2082
2073
  def grouped_search(
@@ -2092,7 +2083,6 @@ class ESCollection(Generic[ModelType]):
2092
2083
  filters=None,
2093
2084
  access_control=None,
2094
2085
  as_obj=True,
2095
- use_archive=False,
2096
2086
  track_total_hits=False,
2097
2087
  ):
2098
2088
  if rows is None:
@@ -2134,7 +2124,7 @@ class ESCollection(Generic[ModelType]):
2134
2124
  if filters:
2135
2125
  args.append(("filters", filters))
2136
2126
 
2137
- result = self._search(args, use_archive=use_archive, track_total_hits=track_total_hits)
2127
+ result = self._search(args, track_total_hits=track_total_hits)
2138
2128
 
2139
2129
  return {
2140
2130
  "offset": offset,
@@ -2270,6 +2260,75 @@ class ESCollection(Generic[ModelType]):
2270
2260
  else:
2271
2261
  return True
2272
2262
 
2263
+ def _create_ilm_policy(self, ilm_config):
2264
+ """Create or update the ILM policy for this collection.
2265
+
2266
+ Builds an ILM policy with hot (rollover), optional warm (forcemerge),
2267
+ and optional cold phases. No delete phase — retention is handled by
2268
+ the retention cronjob.
2269
+
2270
+ :param ilm_config: The global ILMConfig with rollover settings.
2271
+ """
2272
+ phases: dict[str, Any] = {
2273
+ "hot": {
2274
+ "min_age": "0ms",
2275
+ "actions": {
2276
+ "rollover": {
2277
+ "max_age": ilm_config.rollover_max_age,
2278
+ "max_primary_shard_size": ilm_config.rollover_max_size,
2279
+ }
2280
+ },
2281
+ }
2282
+ }
2283
+
2284
+ if self.ilm_config and self.ilm_config.warm:
2285
+ phases["warm"] = {
2286
+ "min_age": self.ilm_config.warm,
2287
+ "actions": {
2288
+ "forcemerge": {"max_num_segments": 1},
2289
+ },
2290
+ }
2291
+
2292
+ if self.ilm_config and self.ilm_config.cold:
2293
+ phases["cold"] = {
2294
+ "min_age": self.ilm_config.cold,
2295
+ "actions": {},
2296
+ }
2297
+
2298
+ policy = {"phases": phases}
2299
+
2300
+ self.with_retries(
2301
+ self.datastore.client.ilm.put_lifecycle,
2302
+ name=f"{self.name}_policy",
2303
+ policy=policy,
2304
+ )
2305
+ logger.info("ILM policy %s_policy created/updated", self.name)
2306
+
2307
+ def _create_index_template(self, ilm_config):
2308
+ """Create or update a composable index template for ILM-managed rollover.
2309
+
2310
+ The template matches '{name}-*' and includes the full ODM mappings
2311
+ so that rollover indices inherit the correct schema.
2312
+
2313
+ :param ilm_config: The global ILMConfig (unused directly but kept for symmetry).
2314
+ """
2315
+ settings = self._get_index_settings()
2316
+ settings["index"]["lifecycle.name"] = f"{self.name}_policy"
2317
+ settings["index"]["lifecycle.rollover_alias"] = self.name
2318
+
2319
+ mappings = self._get_index_mappings()
2320
+
2321
+ self.with_retries(
2322
+ self.datastore.client.indices.put_index_template,
2323
+ name=f"{self.name}_template",
2324
+ index_patterns=[f"{self.name}-*"],
2325
+ template={
2326
+ "settings": settings,
2327
+ "mappings": mappings,
2328
+ },
2329
+ )
2330
+ logger.info("Index template %s_template created/updated", self.name)
2331
+
2273
2332
  def _get_index_settings(self) -> dict:
2274
2333
  default_stub: dict = deepcopy(default_index)
2275
2334
  settings: dict = default_stub.pop("settings", {})
@@ -2386,8 +2445,15 @@ class ESCollection(Generic[ModelType]):
2386
2445
  """This function should test if the collection that you are trying to access does indeed exist
2387
2446
  and should create it if it does not.
2388
2447
 
2448
+ When ILM is configured for this collection, it sets up the ILM policy,
2449
+ composable index template, and bootstraps a rollover alias instead of
2450
+ using the legacy _hot index naming.
2451
+
2389
2452
  :return:
2390
2453
  """
2454
+ if self.ilm_config:
2455
+ return self._ensure_collection_ilm()
2456
+
2391
2457
  # Create HOT index
2392
2458
  if not self.with_retries(self.datastore.client.indices.exists, index=self.name):
2393
2459
  logger.debug("Index %s does not exist. Creating it now...", self.name.upper())
@@ -2430,6 +2496,116 @@ class ESCollection(Generic[ModelType]):
2430
2496
 
2431
2497
  self._check_fields()
2432
2498
 
2499
+ def _ensure_collection_ilm(self):
2500
+ """Bootstrap an ILM-managed collection with rollover alias.
2501
+
2502
+ 1. Create/update the ILM policy and composable index template.
2503
+ 2. Bootstrap the initial index if needed:
2504
+ - If ILM indices already exist (pattern {name}-0*), skip.
2505
+ - If a legacy _hot index exists, migrate it to {name}-000001.
2506
+ - Otherwise, create {name}-000001 from scratch.
2507
+ """
2508
+ from howler.odm.models.config import config as _config
2509
+
2510
+ ilm_global = _config.datastore.ilm
2511
+
2512
+ # Idempotent: create/update ILM policy and index template
2513
+ self._create_ilm_policy(ilm_global)
2514
+ self._create_index_template(ilm_global)
2515
+
2516
+ ilm_initial_index = f"{self.name}-000001"
2517
+
2518
+ # Check if any ILM-managed index already exists
2519
+ existing_ilm_indices = list(
2520
+ self.with_retries(
2521
+ self.datastore.client.indices.get, index=f"{self.name}-0*", ignore_unavailable=True
2522
+ ).keys()
2523
+ )
2524
+
2525
+ if existing_ilm_indices:
2526
+ # ILM already bootstrapped — ensure the alias exists
2527
+ if not self.with_retries(self.datastore.client.indices.exists_alias, name=self.name):
2528
+ # Find the latest index to set as write index
2529
+ latest = sorted(existing_ilm_indices)[-1]
2530
+ self.with_retries(
2531
+ self.datastore.client.indices.put_alias,
2532
+ index=latest,
2533
+ name=self.name,
2534
+ is_write_index=True,
2535
+ )
2536
+ logger.debug("ILM collection %s already bootstrapped", self.name.upper())
2537
+ elif self.with_retries(self.datastore.client.indices.exists, index=self.index_name):
2538
+ # Legacy _hot index exists — migrate to ILM
2539
+ logger.info("Migrating %s from legacy _hot index to ILM-managed rollover", self.name.upper())
2540
+
2541
+ # Block writes on the old index
2542
+ self.with_retries(
2543
+ self.datastore.client.indices.put_settings,
2544
+ index=self.index_name,
2545
+ settings=write_block_settings,
2546
+ )
2547
+
2548
+ # Clone the _hot index to the new ILM initial index
2549
+ self._safe_index_copy(self.datastore.client.indices.clone, self.index_name, ilm_initial_index)
2550
+
2551
+ # Apply ILM settings to the new index
2552
+ self.with_retries(
2553
+ self.datastore.client.indices.put_settings,
2554
+ index=ilm_initial_index,
2555
+ settings={
2556
+ "index.lifecycle.name": f"{self.name}_policy",
2557
+ "index.lifecycle.rollover_alias": self.name,
2558
+ "index.blocks.write": None,
2559
+ },
2560
+ )
2561
+
2562
+ # Swap alias: remove old _hot, add new ILM index as write index
2563
+ actions = [
2564
+ {"add": {"index": ilm_initial_index, "alias": self.name, "is_write_index": True}},
2565
+ ]
2566
+
2567
+ # Remove old alias if it points to _hot
2568
+ if self.with_retries(self.datastore.client.indices.exists_alias, index=self.index_name, name=self.name):
2569
+ actions.append({"remove": {"index": self.index_name, "alias": self.name}})
2570
+
2571
+ self.with_retries(self.datastore.client.indices.update_aliases, actions=actions)
2572
+
2573
+ # Unblock writes on the old index (it stays around until manually removed)
2574
+ self.with_retries(
2575
+ self.datastore.client.indices.put_settings,
2576
+ index=self.index_name,
2577
+ settings=write_unblock_settings,
2578
+ )
2579
+
2580
+ # Update index_name to point to the ILM initial index
2581
+ self.index_name = ilm_initial_index
2582
+
2583
+ logger.info("Migration of %s to ILM complete", self.name.upper())
2584
+ else:
2585
+ # Fresh install — create the initial ILM index with alias
2586
+ logger.debug("Creating ILM-managed index %s...", ilm_initial_index)
2587
+ settings = self._get_index_settings()
2588
+ settings["index"]["lifecycle.name"] = f"{self.name}_policy"
2589
+ settings["index"]["lifecycle.rollover_alias"] = self.name
2590
+
2591
+ try:
2592
+ self.with_retries(
2593
+ self.datastore.client.indices.create,
2594
+ index=ilm_initial_index,
2595
+ mappings=self._get_index_mappings(),
2596
+ settings=settings,
2597
+ aliases={self.name: {"is_write_index": True}},
2598
+ )
2599
+ except elasticsearch.exceptions.RequestError as e:
2600
+ if "resource_already_exists_exception" not in str(e):
2601
+ raise
2602
+ logger.warning("ILM index already exists: %s", ilm_initial_index)
2603
+
2604
+ # Update index_name to point to the ILM initial index
2605
+ self.index_name = ilm_initial_index
2606
+
2607
+ self._check_fields()
2608
+
2433
2609
  def _add_fields(self, missing_fields: Dict):
2434
2610
  no_fix = []
2435
2611
  properties = {}
@@ -2467,6 +2643,13 @@ class ESCollection(Generic[ModelType]):
2467
2643
  **recursive_update(current_template, {"mappings": {"properties": properties}}),
2468
2644
  )
2469
2645
 
2646
+ # When ILM is enabled, also update the composable index template so
2647
+ # future rollover indices inherit the new field mappings.
2648
+ if self.ilm_config:
2649
+ from howler.odm.models.config import config as _config
2650
+
2651
+ self._create_index_template(_config.datastore.ilm)
2652
+
2470
2653
  def wipe(self):
2471
2654
  """This function should completely delete the collection
2472
2655
 
@@ -51,7 +51,8 @@ class HowlerDatastore(object):
51
51
  )
52
52
 
53
53
  for _index, _odm in INDEXES:
54
- self.ds.register(_index, _odm)
54
+ ilm_index_config = config.datastore.ilm.indices.get(_index) if config.datastore.ilm.enabled else None
55
+ self.ds.register(_index, _odm, ilm_config=ilm_index_config)
55
56
 
56
57
  def __enter__(self):
57
58
  return self
@@ -166,10 +166,14 @@ class ESStore(object):
166
166
  KeyError: If *name* has not been registered via ``register``.
167
167
  """
168
168
  if not self.validate:
169
- return ESCollection(self, name, model_class=self._models[name], validate=self.validate)
169
+ ilm_cfg = self.__dict__.get("_ilm_configs", {}).get(name)
170
+ return ESCollection(self, name, model_class=self._models[name], validate=self.validate, ilm_config=ilm_cfg)
170
171
 
171
172
  if name not in self._collections:
172
- self._collections[name] = ESCollection(self, name, model_class=self._models[name], validate=self.validate)
173
+ ilm_cfg = self.__dict__.get("_ilm_configs", {}).get(name)
174
+ self._collections[name] = ESCollection(
175
+ self, name, model_class=self._models[name], validate=self.validate, ilm_config=ilm_cfg
176
+ )
173
177
 
174
178
  return self._collections[name]
175
179
 
@@ -307,7 +311,7 @@ class ESStore(object):
307
311
  """
308
312
  return self.client.ping()
309
313
 
310
- def register(self, name: str, model_class=None):
314
+ def register(self, name: str, model_class=None, ilm_config=None):
311
315
  """Register a collection (index) name and its optional ODM model class.
312
316
 
313
317
  Args:
@@ -315,6 +319,7 @@ class ESStore(object):
315
319
  and underscores.
316
320
  model_class: ODM model class used for validation and serialisation.
317
321
  ``None`` disables model-level validation for this collection.
322
+ ilm_config: Optional per-index ILM configuration (ILMIndexConfig).
318
323
 
319
324
  Raises:
320
325
  DataStoreException: If *name* contains invalid characters.
@@ -326,6 +331,10 @@ class ESStore(object):
326
331
  )
327
332
 
328
333
  self._models[name] = model_class
334
+ if ilm_config is not None:
335
+ if "_ilm_configs" not in self.__dict__:
336
+ self._ilm_configs: dict = {}
337
+ self._ilm_configs[name] = ilm_config
329
338
 
330
339
  def to_pydatemath(self, value):
331
340
  """Convert an internal date-math expression to ES date-math syntax.
@@ -93,6 +93,47 @@ class Host(BaseModel):
93
93
  return self.__repr__()
94
94
 
95
95
 
96
+ class ILMIndexConfig(BaseModel):
97
+ """Per-index ILM phase configuration.
98
+
99
+ Controls when an index transitions to warm and cold phases.
100
+ Values are Elasticsearch age strings (e.g. "30d", "90d").
101
+ """
102
+
103
+ warm: Optional[str] = Field(
104
+ default=None,
105
+ description="Min age before the index enters the warm phase (e.g. '30d'). None to skip.",
106
+ )
107
+ cold: Optional[str] = Field(
108
+ default=None,
109
+ description="Min age before the index enters the cold phase (e.g. '90d'). None to skip.",
110
+ )
111
+
112
+
113
+ class ILMConfig(BaseModel):
114
+ """Index Lifecycle Management configuration.
115
+
116
+ When enabled, Howler uses Elasticsearch ILM policies and rollover aliases
117
+ to split large indices into time-based segments. This cooperates with the
118
+ existing retention cronjob — ILM handles rollover and phase transitions,
119
+ while the retention job handles document deletion.
120
+ """
121
+
122
+ enabled: bool = Field(default=False, description="Enable ILM-based index rollover")
123
+ rollover_max_age: str = Field(
124
+ default="30d",
125
+ description="Maximum age of the write index before rollover (e.g. '30d')",
126
+ )
127
+ rollover_max_size: str = Field(
128
+ default="50gb",
129
+ description="Maximum primary shard size before rollover (e.g. '50gb')",
130
+ )
131
+ indices: dict[str, ILMIndexConfig] = Field(
132
+ default={},
133
+ description="Per-index ILM configuration, keyed by collection name (e.g. 'hit')",
134
+ )
135
+
136
+
96
137
  class Datastore(BaseModel):
97
138
  """Datastore configuration for Howler.
98
139
 
@@ -107,6 +148,10 @@ class Datastore(BaseModel):
107
148
  type: Literal["elasticsearch"] = Field(
108
149
  default="elasticsearch", description="Type of application used for the datastore"
109
150
  )
151
+ ilm: ILMConfig = Field(
152
+ default_factory=ILMConfig,
153
+ description="Index Lifecycle Management configuration",
154
+ )
110
155
 
111
156
 
112
157
  class Logging(BaseModel):
@@ -152,7 +152,7 @@ suppress-none-returning = true
152
152
  [tool.poetry]
153
153
  package-mode = true
154
154
  name = "howler-api"
155
- version = "3.4.0.dev912"
155
+ version = "3.4.0.dev927"
156
156
  description = "Howler - API server"
157
157
  authors = [
158
158
  "Canadian Centre for Cyber Security <howler@cyber.gc.ca>",