insightconnect-plugin-runtime 6.3.3__tar.gz → 6.3.5__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 (94) hide show
  1. {insightconnect_plugin_runtime-6.3.3/insightconnect_plugin_runtime.egg-info → insightconnect_plugin_runtime-6.3.5}/PKG-INFO +13 -11
  2. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/README.md +2 -0
  3. insightconnect_plugin_runtime-6.3.5/insightconnect_plugin_runtime/telemetry.py +193 -0
  4. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/util.py +43 -0
  5. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5/insightconnect_plugin_runtime.egg-info}/PKG-INFO +13 -11
  6. insightconnect_plugin_runtime-6.3.5/insightconnect_plugin_runtime.egg-info/requires.txt +19 -0
  7. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/setup.py +11 -11
  8. insightconnect_plugin_runtime-6.3.3/insightconnect_plugin_runtime/telemetry.py +0 -94
  9. insightconnect_plugin_runtime-6.3.3/insightconnect_plugin_runtime.egg-info/requires.txt +0 -19
  10. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/MANIFEST.in +0 -0
  11. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect-plugin-swagger.json +0 -0
  12. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/__init__.py +0 -0
  13. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/action.py +0 -0
  14. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/api/__init__.py +0 -0
  15. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/api/endpoints.py +0 -0
  16. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/api/schemas.py +0 -0
  17. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/cli.py +0 -0
  18. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/clients/__init__.py +0 -0
  19. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/clients/aws_client.py +0 -0
  20. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/clients/oauth.py +0 -0
  21. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/connection.py +0 -0
  22. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/data/input_message_schema.json +0 -0
  23. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/data/output_message_schema.json +0 -0
  24. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/dispatcher.py +0 -0
  25. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/exceptions.py +0 -0
  26. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/helper.py +0 -0
  27. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/metrics.py +0 -0
  28. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/plugin.py +0 -0
  29. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/schema.py +0 -0
  30. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/server.py +0 -0
  31. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/step.py +0 -0
  32. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/task.py +0 -0
  33. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/trigger.py +0 -0
  34. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime/variables.py +0 -0
  35. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime.egg-info/SOURCES.txt +0 -0
  36. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime.egg-info/dependency_links.txt +0 -0
  37. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/insightconnect_plugin_runtime.egg-info/top_level.txt +0 -0
  38. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/setup.cfg +0 -0
  39. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/__init__.py +0 -0
  40. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/__init__.py +0 -0
  41. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/__init__.py +0 -0
  42. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/__init__.py +0 -0
  43. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/__init__.py +0 -0
  44. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/__init__.py +0 -0
  45. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/hello/__init__.py +0 -0
  46. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/hello/action.py +0 -0
  47. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/hello/schema.py +0 -0
  48. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/return_bad_json/__init__.py +0 -0
  49. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/return_bad_json/action.py +0 -0
  50. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/return_bad_json/schema.py +0 -0
  51. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/throw_exception/__init__.py +0 -0
  52. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/throw_exception/action.py +0 -0
  53. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/throw_exception/schema.py +0 -0
  54. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/connection/__init__.py +0 -0
  55. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/connection/connection.py +0 -0
  56. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/connection/schema.py +0 -0
  57. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/__init__.py +0 -0
  58. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/monitor_events/__init__.py +0 -0
  59. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/monitor_events/schema.py +0 -0
  60. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/monitor_events/task.py +0 -0
  61. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/__init__.py +0 -0
  62. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/hello_trigger/__init__.py +0 -0
  63. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/hello_trigger/schema.py +0 -0
  64. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/hello_trigger/trigger.py +0 -0
  65. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/return_bad_json_trigger/__init__.py +0 -0
  66. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/return_bad_json_trigger/schema.py +0 -0
  67. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/return_bad_json_trigger/trigger.py +0 -0
  68. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/throw_exception_trigger/__init__.py +0 -0
  69. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/throw_exception_trigger/schema.py +0 -0
  70. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/throw_exception_trigger/trigger.py +0 -0
  71. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/komand_hello_world/util/__init__.py +0 -0
  72. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/hello_world/setup.py +0 -0
  73. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/tests/__init__.py +0 -0
  74. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/tests/conftest.py +0 -0
  75. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/tests/test_cli.py +0 -0
  76. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/tests/test_hello_world.py +0 -0
  77. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/plugin/hello_world/tests/test_server.py +0 -0
  78. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/__init__.py +0 -0
  79. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_action.py +0 -0
  80. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_api.py +0 -0
  81. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_aws_action.py +0 -0
  82. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_custom_encoder.py +0 -0
  83. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_endpoints.py +0 -0
  84. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_exceptions.py +0 -0
  85. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_helpers.py +0 -0
  86. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_metrics.py +0 -0
  87. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_oauth.py +0 -0
  88. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_plugin.py +0 -0
  89. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_schema.py +0 -0
  90. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_server_cloud_plugins.py +0 -0
  91. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_server_spec.py +0 -0
  92. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_trigger.py +0 -0
  93. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/test_variables.py +0 -0
  94. {insightconnect_plugin_runtime-6.3.3 → insightconnect_plugin_runtime-6.3.5}/tests/unit/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: insightconnect-plugin-runtime
3
- Version: 6.3.3
3
+ Version: 6.3.5
4
4
  Summary: InsightConnect Plugin Runtime
5
5
  Home-page: https://github.com/rapid7/komand-plugin-sdk-python
6
6
  Author: Rapid7 Integrations Alliance
@@ -12,25 +12,25 @@ Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Natural Language :: English
13
13
  Classifier: Topic :: Software Development :: Build Tools
14
14
  Description-Content-Type: text/markdown
15
- Requires-Dist: requests==2.32.3
15
+ Requires-Dist: requests==2.32.4
16
16
  Requires-Dist: python_jsonschema_objects==0.5.2
17
17
  Requires-Dist: jsonschema==4.22.0
18
- Requires-Dist: certifi==2024.12.14
19
- Requires-Dist: Flask==3.1.0
18
+ Requires-Dist: certifi==2025.4.26
19
+ Requires-Dist: Flask==3.1.1
20
20
  Requires-Dist: gunicorn==23.0.0
21
- Requires-Dist: greenlet==3.1.1
22
- Requires-Dist: gevent==24.11.1
21
+ Requires-Dist: greenlet==3.2.3
22
+ Requires-Dist: gevent==25.5.1
23
23
  Requires-Dist: marshmallow==3.21.0
24
24
  Requires-Dist: apispec==6.5.0
25
25
  Requires-Dist: apispec-webframeworks==1.0.0
26
26
  Requires-Dist: blinker==1.9.0
27
- Requires-Dist: structlog==24.4.0
27
+ Requires-Dist: structlog==25.4.0
28
28
  Requires-Dist: python-json-logger==2.0.7
29
29
  Requires-Dist: Jinja2==3.1.6
30
- Requires-Dist: opentelemetry-sdk==1.31.1
31
- Requires-Dist: opentelemetry-instrumentation-flask==0.52b1
32
- Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.31.1
33
- Requires-Dist: opentelemetry-instrumentation-requests==0.52b1
30
+ Requires-Dist: opentelemetry-sdk==1.34.0
31
+ Requires-Dist: opentelemetry-instrumentation-flask==0.55b0
32
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.34.0
33
+ Requires-Dist: opentelemetry-instrumentation-requests==0.55b0
34
34
  Dynamic: author
35
35
  Dynamic: author-email
36
36
  Dynamic: classifier
@@ -224,6 +224,8 @@ contributed. Black is installed as a test dependency and the hook can be initial
224
224
  after cloning this repository.
225
225
 
226
226
  ## Changelog
227
+ * 6.3.5 - Added `monitor_task_delay` decorator to detect processing delays
228
+ * 6.3.4 - Addressed vulnerabilities within the slim and non-slim Python images (bumping packages)
227
229
  * 6.3.3 - Add helper func for tracing context to preserve parent span in threadpools
228
230
  * 6.3.2 - Raise `APIException` from within the `response_handler` to easily access the status code within the plugin
229
231
  * 6.3.1 - Improved filtering for `custom_config` parameters for plugin tasks
@@ -182,6 +182,8 @@ contributed. Black is installed as a test dependency and the hook can be initial
182
182
  after cloning this repository.
183
183
 
184
184
  ## Changelog
185
+ * 6.3.5 - Added `monitor_task_delay` decorator to detect processing delays
186
+ * 6.3.4 - Addressed vulnerabilities within the slim and non-slim Python images (bumping packages)
185
187
  * 6.3.3 - Add helper func for tracing context to preserve parent span in threadpools
186
188
  * 6.3.2 - Raise `APIException` from within the `response_handler` to easily access the status code within the plugin
187
189
  * 6.3.1 - Improved filtering for `custom_config` parameters for plugin tasks
@@ -0,0 +1,193 @@
1
+ import contextvars
2
+ from datetime import UTC, datetime
3
+ from functools import partial, wraps
4
+ from typing import Any, Callable, List, Union
5
+
6
+ from dateutil.parser import parse
7
+ from flask.app import Flask
8
+ from opentelemetry import trace
9
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
10
+ from opentelemetry.instrumentation.flask import FlaskInstrumentor
11
+ from opentelemetry.instrumentation.requests import RequestsInstrumentor
12
+ from opentelemetry.sdk.resources import Resource
13
+ from opentelemetry.sdk.trace import TracerProvider
14
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
15
+ from opentelemetry.trace import Status, StatusCode
16
+
17
+ from insightconnect_plugin_runtime.plugin import Plugin
18
+ from insightconnect_plugin_runtime.util import (
19
+ OTEL_ENDPOINT,
20
+ is_running_in_cloud,
21
+ parse_from_string,
22
+ )
23
+
24
+
25
+ def init_tracing(app: Flask, plugin: Plugin, endpoint: str) -> None:
26
+ """
27
+ Initialize OpenTelemetry Tracing
28
+
29
+ The function sets up the tracer provider, span processor and exporter with auto-instrumentation
30
+
31
+ :param app: The Flask Application
32
+ :param plugin: The plugin to derive the service name from
33
+ :param endpoint: The Otel Endpoint to emit traces to
34
+ """
35
+
36
+ if not is_running_in_cloud():
37
+ return
38
+
39
+ resource = Resource(attributes={"service.name": f'{plugin.name.lower().replace(" ", "_")}-{plugin.version}'})
40
+
41
+ trace_provider = TracerProvider(resource=resource)
42
+ exporter = OTLPSpanExporter(endpoint=endpoint)
43
+ trace_provider.add_span_processor(BatchSpanProcessor(exporter))
44
+ trace.set_tracer_provider(trace_provider)
45
+
46
+ FlaskInstrumentor().instrument_app(app)
47
+
48
+ def requests_callback(span: trace.Span, _: Any, response: Any) -> None:
49
+ if hasattr(response, "status_code"):
50
+ span.set_status(Status(StatusCode.OK if response.status_code < 400 else StatusCode.ERROR))
51
+
52
+ RequestsInstrumentor().instrument(trace_provider=trace_provider, response_hook=requests_callback)
53
+
54
+
55
+ def auto_instrument(func: Callable) -> Callable:
56
+ """
57
+ Decorator that auto-instruments a function with a trace
58
+
59
+ :param func: function to instrument
60
+ :return:
61
+ """
62
+
63
+ @wraps(func)
64
+ def wrapper(*args, **kwargs):
65
+ tracer = trace.get_tracer(__name__)
66
+ with tracer.start_as_current_span(func.__name__):
67
+ return func(*args, **kwargs)
68
+
69
+ return wrapper
70
+
71
+
72
+ def create_post_fork(app_getter: Callable, plugin_getter: Callable, config_getter: Callable) -> Callable:
73
+ def post_fork(server, worker):
74
+ app = app_getter()
75
+ plugin = plugin_getter()
76
+ endpoint = config_getter().get(OTEL_ENDPOINT, None)
77
+ if endpoint:
78
+ init_tracing(app, plugin, endpoint)
79
+
80
+ return post_fork
81
+
82
+
83
+ def with_context(context: contextvars.Context, function: Callable) -> Callable:
84
+ """
85
+ Creates a wrapper function that executes the target function with the specified context.
86
+
87
+ :param context: The Context object to apply when executing the function
88
+ :type context: contextvars.Context
89
+
90
+ :param function: The function to wrap with the specified context
91
+ :type function: Callable
92
+
93
+ :return: A wrapper function that applies the context when called
94
+ :rtype: Callable
95
+ """
96
+
97
+ def _wrapper(context_: contextvars.Context, function_: Callable, *args, **kwargs):
98
+ return context_.copy().run(function_, *args, **kwargs)
99
+
100
+ return partial(_wrapper, context, function)
101
+
102
+
103
+ def monitor_task_delay(
104
+ timestamp_keys: Union[str, List[str]], default_delay_threshold: str = "2d"
105
+ ) -> Callable:
106
+ """Monitor timestamp fields in task state to detect processing delays.
107
+
108
+ This decorator checks if specified timestamp fields in a task's state have fallen
109
+ behind a configurable threshold, indicating the task is processing data with a lag.
110
+ When timestamps fall behind the threshold, an error is logged.
111
+
112
+ The threshold can be overridden at runtime by setting the "task_delay_threshold" key
113
+ in the task's custom_config.
114
+
115
+ :param timestamp_keys: One or more state keys containing timestamps to monitor
116
+ :type timestamp_keys: Union[str, List[str]]
117
+
118
+ :param default_delay_threshold: Time duration string representing maximum acceptable lag (e.g. "2d" for 2 days).
119
+ :type default_delay_threshold: str
120
+
121
+ :return: Decorator function that wraps the original task function
122
+ :rtype: Callable
123
+ """
124
+
125
+ # Check if time_fields is a string and convert it to a list
126
+ if isinstance(timestamp_keys, str):
127
+ timestamp_keys = [timestamp_keys]
128
+
129
+ def _decorator(function_: Callable):
130
+ @wraps(function_)
131
+ def _wrapper(self, *args, **kwargs):
132
+ # Unpack response tuple from task `def run()` method
133
+ output, state, has_more_pages, status_code, error_object = function_(
134
+ self, *args, **kwargs
135
+ )
136
+
137
+ # Try-except with pass to make sure any exception won't stop the task from running
138
+ try:
139
+ # Check if any time fields are in the past
140
+ threshold = kwargs.get("custom_config", {}).get(
141
+ "task_delay_threshold", default_delay_threshold
142
+ )
143
+
144
+ # Calculate the delayed time based on the threshold
145
+ delay_threshold_time = (
146
+ datetime.now(UTC) - parse_from_string(threshold)
147
+ ).replace(tzinfo=None)
148
+
149
+ # Loop over the state time fields and check if they are below the set threshold
150
+ for state_time in timestamp_keys:
151
+ # Check if the state time exists in the state dictionary
152
+ if not (current_state_time := state.get(state_time)):
153
+ continue
154
+
155
+ # Parse and normalize the state time value
156
+ try:
157
+ # First, try to parse the epoch timestamp
158
+ normalized_state_time = datetime.fromtimestamp(
159
+ float(current_state_time)
160
+ )
161
+ except (ValueError, TypeError):
162
+ # If parsing fails, parse as a string
163
+ normalized_state_time = parse(str(current_state_time))
164
+
165
+ # Normalize the state time to UTC and remove timezone info
166
+ normalized_state_time = normalized_state_time.astimezone(
167
+ UTC
168
+ ).replace(tzinfo=None)
169
+
170
+ # If the normalized state time is below the threshold, log an error message
171
+ if normalized_state_time < delay_threshold_time:
172
+ # Log an error message that indicates the integration state time is below the threshold
173
+ self.logger.error(
174
+ f"ERROR: THE INTEGRATION IS FALLING BEHIND",
175
+ field=state_time,
176
+ current_value=current_state_time,
177
+ normalized_time=normalized_state_time,
178
+ threshold_time=delay_threshold_time,
179
+ configured_threshold=threshold,
180
+ has_more_pages=has_more_pages,
181
+ )
182
+ break
183
+ except Exception as error:
184
+ self.logger.info(
185
+ f"An error occurred while checking task delay. Error: {error}"
186
+ )
187
+
188
+ # Return task output
189
+ return output, state, has_more_pages, status_code, error_object
190
+
191
+ return _wrapper
192
+
193
+ return _decorator
@@ -2,7 +2,9 @@
2
2
  import copy
3
3
  import logging
4
4
  import os
5
+ import re
5
6
  import sys
7
+ from datetime import timedelta
6
8
  from typing import Any, Dict, List, Tuple, Union
7
9
 
8
10
  import python_jsonschema_objects as pjs
@@ -261,3 +263,44 @@ def trace():
261
263
 
262
264
  def is_running_in_cloud():
263
265
  return os.environ.get("PLUGIN_RUNTIME_ENVIRONMENT") == "cloud"
266
+
267
+
268
+ def parse_from_string(
269
+ duration: str,
270
+ default: timedelta = timedelta(days=2),
271
+ unit_map: Dict[str, str] = None,
272
+ ) -> timedelta:
273
+ """
274
+ Parse a string duration (i.e. '5d', '12h', '30m', '45s') into a timedelta object.
275
+
276
+ :param duration: String in format like '5d', '12h', '30m', '45s'
277
+ :type duration: str
278
+
279
+ :param default: Default timedelta to return if parsing fails
280
+ :type default: timedelta
281
+
282
+ :param unit_map: Mapping of unit letters to timedelta parameter names
283
+ :type unit_map: Dict[str, str]
284
+
285
+ :return: Parsed timedelta or default if parsing fails
286
+ :rtype: timedelta
287
+ """
288
+
289
+ # If unit_map is not provided, use the default mapping
290
+ if unit_map is None:
291
+ unit_map = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days"}
292
+
293
+ # Check if duration is not empty and is a string
294
+ if not duration or not isinstance(duration, str):
295
+ return default
296
+
297
+ # Use regex to match the duration format
298
+ match = re.match(r"(\d+)([smhd])", duration.lower())
299
+ if not match:
300
+ return default
301
+
302
+ # If match is found, extract the value and unit
303
+ value, unit = match.groups()
304
+
305
+ # Return a timedelta object based on the matched value and unit from the unit_map
306
+ return timedelta(**{unit_map[unit]: int(value)})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: insightconnect-plugin-runtime
3
- Version: 6.3.3
3
+ Version: 6.3.5
4
4
  Summary: InsightConnect Plugin Runtime
5
5
  Home-page: https://github.com/rapid7/komand-plugin-sdk-python
6
6
  Author: Rapid7 Integrations Alliance
@@ -12,25 +12,25 @@ Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Natural Language :: English
13
13
  Classifier: Topic :: Software Development :: Build Tools
14
14
  Description-Content-Type: text/markdown
15
- Requires-Dist: requests==2.32.3
15
+ Requires-Dist: requests==2.32.4
16
16
  Requires-Dist: python_jsonschema_objects==0.5.2
17
17
  Requires-Dist: jsonschema==4.22.0
18
- Requires-Dist: certifi==2024.12.14
19
- Requires-Dist: Flask==3.1.0
18
+ Requires-Dist: certifi==2025.4.26
19
+ Requires-Dist: Flask==3.1.1
20
20
  Requires-Dist: gunicorn==23.0.0
21
- Requires-Dist: greenlet==3.1.1
22
- Requires-Dist: gevent==24.11.1
21
+ Requires-Dist: greenlet==3.2.3
22
+ Requires-Dist: gevent==25.5.1
23
23
  Requires-Dist: marshmallow==3.21.0
24
24
  Requires-Dist: apispec==6.5.0
25
25
  Requires-Dist: apispec-webframeworks==1.0.0
26
26
  Requires-Dist: blinker==1.9.0
27
- Requires-Dist: structlog==24.4.0
27
+ Requires-Dist: structlog==25.4.0
28
28
  Requires-Dist: python-json-logger==2.0.7
29
29
  Requires-Dist: Jinja2==3.1.6
30
- Requires-Dist: opentelemetry-sdk==1.31.1
31
- Requires-Dist: opentelemetry-instrumentation-flask==0.52b1
32
- Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.31.1
33
- Requires-Dist: opentelemetry-instrumentation-requests==0.52b1
30
+ Requires-Dist: opentelemetry-sdk==1.34.0
31
+ Requires-Dist: opentelemetry-instrumentation-flask==0.55b0
32
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.34.0
33
+ Requires-Dist: opentelemetry-instrumentation-requests==0.55b0
34
34
  Dynamic: author
35
35
  Dynamic: author-email
36
36
  Dynamic: classifier
@@ -224,6 +224,8 @@ contributed. Black is installed as a test dependency and the hook can be initial
224
224
  after cloning this repository.
225
225
 
226
226
  ## Changelog
227
+ * 6.3.5 - Added `monitor_task_delay` decorator to detect processing delays
228
+ * 6.3.4 - Addressed vulnerabilities within the slim and non-slim Python images (bumping packages)
227
229
  * 6.3.3 - Add helper func for tracing context to preserve parent span in threadpools
228
230
  * 6.3.2 - Raise `APIException` from within the `response_handler` to easily access the status code within the plugin
229
231
  * 6.3.1 - Improved filtering for `custom_config` parameters for plugin tasks
@@ -0,0 +1,19 @@
1
+ requests==2.32.4
2
+ python_jsonschema_objects==0.5.2
3
+ jsonschema==4.22.0
4
+ certifi==2025.4.26
5
+ Flask==3.1.1
6
+ gunicorn==23.0.0
7
+ greenlet==3.2.3
8
+ gevent==25.5.1
9
+ marshmallow==3.21.0
10
+ apispec==6.5.0
11
+ apispec-webframeworks==1.0.0
12
+ blinker==1.9.0
13
+ structlog==25.4.0
14
+ python-json-logger==2.0.7
15
+ Jinja2==3.1.6
16
+ opentelemetry-sdk==1.34.0
17
+ opentelemetry-instrumentation-flask==0.55b0
18
+ opentelemetry-exporter-otlp-proto-http==1.34.0
19
+ opentelemetry-instrumentation-requests==0.55b0
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name="insightconnect-plugin-runtime",
8
- version="6.3.3",
8
+ version="6.3.5",
9
9
  description="InsightConnect Plugin Runtime",
10
10
  long_description=long_description,
11
11
  long_description_content_type="text/markdown",
@@ -14,25 +14,25 @@ setup(
14
14
  url="https://github.com/rapid7/komand-plugin-sdk-python",
15
15
  packages=find_packages(),
16
16
  install_requires=[
17
- "requests==2.32.3",
17
+ "requests==2.32.4",
18
18
  "python_jsonschema_objects==0.5.2",
19
19
  "jsonschema==4.22.0",
20
- "certifi==2024.12.14",
21
- "Flask==3.1.0",
20
+ "certifi==2025.4.26",
21
+ "Flask==3.1.1",
22
22
  "gunicorn==23.0.0",
23
- "greenlet==3.1.1",
24
- "gevent==24.11.1",
23
+ "greenlet==3.2.3",
24
+ "gevent==25.5.1",
25
25
  "marshmallow==3.21.0",
26
26
  "apispec==6.5.0",
27
27
  "apispec-webframeworks==1.0.0",
28
28
  "blinker==1.9.0",
29
- "structlog==24.4.0",
29
+ "structlog==25.4.0",
30
30
  "python-json-logger==2.0.7",
31
31
  "Jinja2==3.1.6",
32
- "opentelemetry-sdk==1.31.1",
33
- "opentelemetry-instrumentation-flask==0.52b1",
34
- "opentelemetry-exporter-otlp-proto-http==1.31.1",
35
- "opentelemetry-instrumentation-requests==0.52b1",
32
+ "opentelemetry-sdk==1.34.0",
33
+ "opentelemetry-instrumentation-flask==0.55b0",
34
+ "opentelemetry-exporter-otlp-proto-http==1.34.0",
35
+ "opentelemetry-instrumentation-requests==0.55b0",
36
36
  ],
37
37
  tests_require=[
38
38
  "pytest",
@@ -1,94 +0,0 @@
1
- import contextvars
2
- from functools import wraps, partial
3
- from typing import Any, Callable
4
-
5
- from flask.app import Flask
6
- from opentelemetry import trace
7
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
8
- from opentelemetry.instrumentation.flask import FlaskInstrumentor
9
- from opentelemetry.instrumentation.requests import RequestsInstrumentor
10
- from opentelemetry.sdk.resources import Resource
11
- from opentelemetry.sdk.trace import TracerProvider
12
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
13
- from opentelemetry.trace import Status, StatusCode
14
-
15
- from insightconnect_plugin_runtime.plugin import Plugin
16
- from insightconnect_plugin_runtime.util import is_running_in_cloud, OTEL_ENDPOINT
17
-
18
-
19
- def init_tracing(app: Flask, plugin: Plugin, endpoint: str) -> None:
20
- """
21
- Initialize OpenTelemetry Tracing
22
-
23
- The function sets up the tracer provider, span processor and exporter with auto-instrumentation
24
-
25
- :param app: The Flask Application
26
- :param plugin: The plugin to derive the service name from
27
- :param endpoint: The Otel Endpoint to emit traces to
28
- """
29
-
30
- if not is_running_in_cloud():
31
- return
32
-
33
- resource = Resource(attributes={"service.name": f'{plugin.name.lower().replace(" ", "_")}-{plugin.version}'})
34
-
35
- trace_provider = TracerProvider(resource=resource)
36
- exporter = OTLPSpanExporter(endpoint=endpoint)
37
- trace_provider.add_span_processor(BatchSpanProcessor(exporter))
38
- trace.set_tracer_provider(trace_provider)
39
-
40
- FlaskInstrumentor().instrument_app(app)
41
-
42
- def requests_callback(span: trace.Span, _: Any, response: Any) -> None:
43
- if hasattr(response, "status_code"):
44
- span.set_status(Status(StatusCode.OK if response.status_code < 400 else StatusCode.ERROR))
45
-
46
- RequestsInstrumentor().instrument(trace_provider=trace_provider, response_hook=requests_callback)
47
-
48
-
49
- def auto_instrument(func: Callable) -> Callable:
50
- """
51
- Decorator that auto-instruments a function with a trace
52
-
53
- :param func: function to instrument
54
- :return:
55
- """
56
-
57
- @wraps(func)
58
- def wrapper(*args, **kwargs):
59
- tracer = trace.get_tracer(__name__)
60
- with tracer.start_as_current_span(func.__name__):
61
- return func(*args, **kwargs)
62
-
63
- return wrapper
64
-
65
-
66
- def create_post_fork(app_getter: Callable, plugin_getter: Callable, config_getter: Callable) -> Callable:
67
- def post_fork(server, worker):
68
- app = app_getter()
69
- plugin = plugin_getter()
70
- endpoint = config_getter().get(OTEL_ENDPOINT, None)
71
- if endpoint:
72
- init_tracing(app, plugin, endpoint)
73
-
74
- return post_fork
75
-
76
-
77
- def with_context(context: contextvars.Context, function: Callable) -> Callable:
78
- """
79
- Creates a wrapper function that executes the target function with the specified context.
80
-
81
- :param context: The Context object to apply when executing the function
82
- :type context: contextvars.Context
83
-
84
- :param function: The function to wrap with the specified context
85
- :type function: Callable
86
-
87
- :return: A wrapper function that applies the context when called
88
- :rtype: Callable
89
- """
90
-
91
- def _wrapper(context_: contextvars.Context, function_: Callable, *args, **kwargs):
92
- return context_.copy().run(function_, *args, **kwargs)
93
-
94
- return partial(_wrapper, context, function)
@@ -1,19 +0,0 @@
1
- requests==2.32.3
2
- python_jsonschema_objects==0.5.2
3
- jsonschema==4.22.0
4
- certifi==2024.12.14
5
- Flask==3.1.0
6
- gunicorn==23.0.0
7
- greenlet==3.1.1
8
- gevent==24.11.1
9
- marshmallow==3.21.0
10
- apispec==6.5.0
11
- apispec-webframeworks==1.0.0
12
- blinker==1.9.0
13
- structlog==24.4.0
14
- python-json-logger==2.0.7
15
- Jinja2==3.1.6
16
- opentelemetry-sdk==1.31.1
17
- opentelemetry-instrumentation-flask==0.52b1
18
- opentelemetry-exporter-otlp-proto-http==1.31.1
19
- opentelemetry-instrumentation-requests==0.52b1