insightconnect-plugin-runtime 5.4.2__py3-none-any.whl → 5.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- insightconnect_plugin_runtime/plugin.py +19 -9
- insightconnect_plugin_runtime/server.py +7 -2
- insightconnect_plugin_runtime/util.py +15 -0
- {insightconnect_plugin_runtime-5.4.2.dist-info → insightconnect_plugin_runtime-5.4.4.dist-info}/METADATA +9 -8
- {insightconnect_plugin_runtime-5.4.2.dist-info → insightconnect_plugin_runtime-5.4.4.dist-info}/RECORD +10 -10
- tests/plugin/__init__.py +0 -1
- tests/unit/test_server_cloud_plugins.py +27 -9
- tests/unit/utils.py +4 -4
- {insightconnect_plugin_runtime-5.4.2.dist-info → insightconnect_plugin_runtime-5.4.4.dist-info}/WHEEL +0 -0
- {insightconnect_plugin_runtime-5.4.2.dist-info → insightconnect_plugin_runtime-5.4.4.dist-info}/top_level.txt +0 -0
|
@@ -18,7 +18,7 @@ from insightconnect_plugin_runtime.exceptions import (
|
|
|
18
18
|
PluginException,
|
|
19
19
|
)
|
|
20
20
|
from insightconnect_plugin_runtime.metrics import MetricsBuilder
|
|
21
|
-
from insightconnect_plugin_runtime.util import is_running_in_cloud
|
|
21
|
+
from insightconnect_plugin_runtime.util import is_running_in_cloud, flush_logging_handlers
|
|
22
22
|
|
|
23
23
|
message_output_type = {
|
|
24
24
|
"action_start": "action_event",
|
|
@@ -374,8 +374,12 @@ class Plugin(object):
|
|
|
374
374
|
log_stream = io.StringIO()
|
|
375
375
|
stream_handler = logging.StreamHandler(log_stream)
|
|
376
376
|
stream_handler.setLevel(logging.DEBUG if is_debug else logging.INFO)
|
|
377
|
-
|
|
378
|
-
logger
|
|
377
|
+
|
|
378
|
+
# Getting logger instances, struct_logger is formatted and mainly used in actions, triggers, and tasks
|
|
379
|
+
# while plugin_logger is the same instance that is used to add/remove and flush handlers during step execution
|
|
380
|
+
struct_logger = structlog.get_logger("plugin")
|
|
381
|
+
plugin_logger = logging.getLogger("plugin")
|
|
382
|
+
plugin_logger.addHandler(stream_handler)
|
|
379
383
|
|
|
380
384
|
success = True
|
|
381
385
|
caught_exception = None
|
|
@@ -409,7 +413,7 @@ class Plugin(object):
|
|
|
409
413
|
output = self.start_step(
|
|
410
414
|
input_message["body"],
|
|
411
415
|
"action",
|
|
412
|
-
|
|
416
|
+
struct_logger,
|
|
413
417
|
log_stream,
|
|
414
418
|
is_test,
|
|
415
419
|
is_debug,
|
|
@@ -419,7 +423,7 @@ class Plugin(object):
|
|
|
419
423
|
output = self.start_step(
|
|
420
424
|
input_message["body"],
|
|
421
425
|
"trigger",
|
|
422
|
-
|
|
426
|
+
struct_logger,
|
|
423
427
|
log_stream,
|
|
424
428
|
is_test,
|
|
425
429
|
is_debug,
|
|
@@ -431,7 +435,7 @@ class Plugin(object):
|
|
|
431
435
|
output = self.start_step(
|
|
432
436
|
input_message["body"],
|
|
433
437
|
"task",
|
|
434
|
-
|
|
438
|
+
struct_logger,
|
|
435
439
|
log_stream,
|
|
436
440
|
is_test,
|
|
437
441
|
is_debug,
|
|
@@ -440,7 +444,7 @@ class Plugin(object):
|
|
|
440
444
|
output, state, has_more_pages, status_code, error_object = self.start_step(
|
|
441
445
|
input_message["body"],
|
|
442
446
|
"task",
|
|
443
|
-
|
|
447
|
+
struct_logger,
|
|
444
448
|
log_stream,
|
|
445
449
|
is_test,
|
|
446
450
|
is_debug,
|
|
@@ -450,7 +454,7 @@ class Plugin(object):
|
|
|
450
454
|
output = self.start_step(
|
|
451
455
|
input_message["body"],
|
|
452
456
|
"connection_test",
|
|
453
|
-
|
|
457
|
+
struct_logger,
|
|
454
458
|
log_stream,
|
|
455
459
|
is_test,
|
|
456
460
|
is_debug,
|
|
@@ -459,7 +463,7 @@ class Plugin(object):
|
|
|
459
463
|
except (ClientException, ServerException, PluginException, Exception) as e:
|
|
460
464
|
success = False
|
|
461
465
|
caught_exception = e
|
|
462
|
-
|
|
466
|
+
struct_logger.exception(e)
|
|
463
467
|
finally:
|
|
464
468
|
output = self.envelope(
|
|
465
469
|
out_type,
|
|
@@ -475,6 +479,11 @@ class Plugin(object):
|
|
|
475
479
|
caught_exception,
|
|
476
480
|
is_test,
|
|
477
481
|
)
|
|
482
|
+
|
|
483
|
+
# Flush all the handlers and remove the one that was previously created
|
|
484
|
+
flush_logging_handlers(plugin_logger)
|
|
485
|
+
plugin_logger.removeHandler(stream_handler)
|
|
486
|
+
|
|
478
487
|
if not success:
|
|
479
488
|
raise LoggedException(caught_exception, output)
|
|
480
489
|
return output
|
|
@@ -497,6 +506,7 @@ class Plugin(object):
|
|
|
497
506
|
:param log_stream the raw stream for the log
|
|
498
507
|
:param is_test: True if the action's test method should execute
|
|
499
508
|
:param is_debug: True if debug is enabled
|
|
509
|
+
:param is_connection_test: True if connection test is running
|
|
500
510
|
:return: An action_event message
|
|
501
511
|
"""
|
|
502
512
|
connection = self.connection_cache.get(message_body["connection"], logger)
|
|
@@ -14,7 +14,7 @@ from gunicorn.arbiter import Arbiter
|
|
|
14
14
|
import structlog
|
|
15
15
|
from pythonjsonlogger.jsonlogger import JsonFormatter
|
|
16
16
|
from requests import get as request_get
|
|
17
|
-
from requests.exceptions import HTTPError, MissingSchema, Timeout, JSONDecodeError
|
|
17
|
+
from requests.exceptions import HTTPError, MissingSchema, Timeout, JSONDecodeError, ConnectionError
|
|
18
18
|
from time import sleep
|
|
19
19
|
from werkzeug.utils import secure_filename
|
|
20
20
|
|
|
@@ -194,7 +194,7 @@ class PluginServer(gunicorn.app.base.BaseApplication):
|
|
|
194
194
|
|
|
195
195
|
self.config_options = plugin_config
|
|
196
196
|
self.logger.info("Plugin configuration successfully retrieved...")
|
|
197
|
-
|
|
197
|
+
return
|
|
198
198
|
except MissingSchema as missing_schema:
|
|
199
199
|
self.logger.error(f"Invalid URL being requested: {CPS_ENDPOINT}, error={missing_schema}")
|
|
200
200
|
except Timeout as timeout:
|
|
@@ -203,9 +203,14 @@ class PluginServer(gunicorn.app.base.BaseApplication):
|
|
|
203
203
|
self.logger.error(f"Connection error when trying to reach CPS. CPS={CPS_ENDPOINT}, error={http_error}")
|
|
204
204
|
except JSONDecodeError as bad_json:
|
|
205
205
|
self.logger.error(f"Got bad JSON back. Response content={request_response.content}, error={bad_json}")
|
|
206
|
+
except ConnectionError as http_connection_pool_error:
|
|
207
|
+
self.logger.info(
|
|
208
|
+
"Connection refused when trying to reach CPS, retrying as the connection is still establishing."
|
|
209
|
+
)
|
|
206
210
|
except Exception as error:
|
|
207
211
|
self.logger.error(f"Hit an unexpected error when retrieving plugin custom configs, error={error}")
|
|
208
212
|
sleep(RETRY_SLEEP)
|
|
213
|
+
self.logger.error(f"Unable to reach CPS after {CPS_RETRY} attempts. CPS={CPS_ENDPOINT}")
|
|
209
214
|
|
|
210
215
|
def register_api_spec(self):
|
|
211
216
|
"""Register all swagger schema definitions and path objects"""
|
|
@@ -139,6 +139,21 @@ class OutputMasker:
|
|
|
139
139
|
return masked_response
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def flush_logging_handlers(logger: logging.Logger) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Flush all the logging handlers attached to the logger.
|
|
145
|
+
|
|
146
|
+
:param logger: The logger object to flush the handlers for.
|
|
147
|
+
:type: logging.Logger
|
|
148
|
+
|
|
149
|
+
:return: None
|
|
150
|
+
:rtype: None
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
for handler in logger.handlers:
|
|
154
|
+
handler.flush()
|
|
155
|
+
|
|
156
|
+
|
|
142
157
|
def default_for_object(obj, defs):
|
|
143
158
|
defaults = {}
|
|
144
159
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: insightconnect-plugin-runtime
|
|
3
|
-
Version: 5.4.
|
|
3
|
+
Version: 5.4.4
|
|
4
4
|
Summary: InsightConnect Plugin Runtime
|
|
5
5
|
Home-page: https://github.com/rapid7/komand-plugin-sdk-python
|
|
6
6
|
Author: Rapid7 Integrations Alliance
|
|
@@ -14,14 +14,14 @@ Classifier: Topic :: Software Development :: Build Tools
|
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
Requires-Dist: requests ==2.31.0
|
|
16
16
|
Requires-Dist: python-jsonschema-objects ==0.5.2
|
|
17
|
-
Requires-Dist: jsonschema ==4.21.
|
|
18
|
-
Requires-Dist: certifi ==
|
|
19
|
-
Requires-Dist: Flask ==3.0.
|
|
17
|
+
Requires-Dist: jsonschema ==4.21.1
|
|
18
|
+
Requires-Dist: certifi ==2024.2.2
|
|
19
|
+
Requires-Dist: Flask ==3.0.2
|
|
20
20
|
Requires-Dist: gunicorn ==21.2.0
|
|
21
21
|
Requires-Dist: greenlet ==3.0.3
|
|
22
|
-
Requires-Dist: gevent ==
|
|
23
|
-
Requires-Dist: marshmallow ==3.
|
|
24
|
-
Requires-Dist: apispec ==6.
|
|
22
|
+
Requires-Dist: gevent ==24.2.1
|
|
23
|
+
Requires-Dist: marshmallow ==3.21.0
|
|
24
|
+
Requires-Dist: apispec ==6.5.0
|
|
25
25
|
Requires-Dist: apispec-webframeworks ==1.0.0
|
|
26
26
|
Requires-Dist: blinker ==1.7.0
|
|
27
27
|
Requires-Dist: structlog ==24.1.0
|
|
@@ -211,7 +211,8 @@ contributed. Black is installed as a test dependency and the hook can be initial
|
|
|
211
211
|
after cloning this repository.
|
|
212
212
|
|
|
213
213
|
## Changelog
|
|
214
|
-
|
|
214
|
+
* 5.4.4 - Handling the 'Connection Refused' to cps (during startup) as an info instead of error log as this is an expected error.
|
|
215
|
+
* 5.4.3 - Updated dependencies to the latest versions | Fixed issue related to logger `StreamHandlers`
|
|
215
216
|
* 5.4.2 - Remove background scheduler to simplify when the server gets custom_config values | Revert to use `bullseye` for slim image.
|
|
216
217
|
* 5.4.1 - Retry logic added to get values from external API for custom_config values.
|
|
217
218
|
* 5.4.0 - Implementation of custom_config parameter for plugin tasks | Alpine image updated OpenSSL and expat | Use `bookworm` for slim image.
|
|
@@ -6,13 +6,13 @@ insightconnect_plugin_runtime/dispatcher.py,sha256=ru7njnyyWE1-oD-VbZJ-Z8tELwvDf
|
|
|
6
6
|
insightconnect_plugin_runtime/exceptions.py,sha256=rC74M9aCSrY7J-nq9ttsccLkNbHR0gbZ2SRvXlCgLx0,6507
|
|
7
7
|
insightconnect_plugin_runtime/helper.py,sha256=m5PxN04-NPXM1X10S2wwjqmiLvnNntd6TnwLoW4nnus,21108
|
|
8
8
|
insightconnect_plugin_runtime/metrics.py,sha256=hf_Aoufip_s4k4o8Gtzz90ymZthkaT2e5sXh5B4LcF0,3186
|
|
9
|
-
insightconnect_plugin_runtime/plugin.py,sha256=
|
|
9
|
+
insightconnect_plugin_runtime/plugin.py,sha256=0IcyhAxklL8lMX7qRXBJ0V6rCt56b5VyrqBG3RAOatU,23573
|
|
10
10
|
insightconnect_plugin_runtime/schema.py,sha256=jTNc6KAMqFpaDVWrAYhkVC6e8I63P3X7uVlJkAr1hiY,583
|
|
11
|
-
insightconnect_plugin_runtime/server.py,sha256=
|
|
11
|
+
insightconnect_plugin_runtime/server.py,sha256=Qf2LF4_6VDRK55xzNXpd5znwe78gA26wOWbvlmvp4Ro,12141
|
|
12
12
|
insightconnect_plugin_runtime/step.py,sha256=KdERg-789-s99IEKN61DR08naz-YPxyinPT0C_T81C4,855
|
|
13
13
|
insightconnect_plugin_runtime/task.py,sha256=d-H1EAzVnmSdDEJtXyIK5JySprxpF9cetVoFGtWlHrg,123
|
|
14
14
|
insightconnect_plugin_runtime/trigger.py,sha256=Zq3cy68N3QxAGbNZKCID6CZF05Zi7YD2sdy_qbedUY8,874
|
|
15
|
-
insightconnect_plugin_runtime/util.py,sha256=
|
|
15
|
+
insightconnect_plugin_runtime/util.py,sha256=msLDZrVDNaOY_lnkZghZk-GKsKpJHOpxz5iYIE8DnQ8,8398
|
|
16
16
|
insightconnect_plugin_runtime/variables.py,sha256=7FjJGnU7KUR7m9o-_tRq7Q3KiaB1Pp0Apj1NGgOwrJk,3056
|
|
17
17
|
insightconnect_plugin_runtime/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
insightconnect_plugin_runtime/api/endpoints.py,sha256=HjiGmYlg8T7_Q_2JHiywMWnE3_mrLfnmbLSj9zFwoSE,31445
|
|
@@ -23,7 +23,7 @@ insightconnect_plugin_runtime/clients/oauth.py,sha256=bWtAGRMwdK4dw9vMPcw9usklyI
|
|
|
23
23
|
insightconnect_plugin_runtime/data/input_message_schema.json,sha256=7_BcHi6UOBiVWGrrJHHn5IoddteXjL7GOKETdO9T2DE,1770
|
|
24
24
|
insightconnect_plugin_runtime/data/output_message_schema.json,sha256=Qya6U-NR5MfOlw4V98VpQzGBVq75eGMUQhI-j3yxOHI,1137
|
|
25
25
|
tests/__init__.py,sha256=uKG3ssG-d51oNHQxKheE9Wzcusd7OW3ei6EYT3WxPnw,106
|
|
26
|
-
tests/plugin/__init__.py,sha256=
|
|
26
|
+
tests/plugin/__init__.py,sha256=Qw_OZORrBIvkyyV04gocNa5BUgVrvt8XmXM3_iIzyD0,2712
|
|
27
27
|
tests/plugin/hello_world/__init__.py,sha256=fzr7Uek0yDCKBchW3fgtv1IAp8JIlW335GwENk4-wPk,42
|
|
28
28
|
tests/plugin/hello_world/hello_world/__init__.py,sha256=lJWXmKWkacbMKKfq-vXx2iTlNfJtmq-jVhOlRfUWn2w,1110
|
|
29
29
|
tests/plugin/hello_world/hello_world/setup.py,sha256=-NAHjSoYB-qU4fZrs_0nHzN7OxkSJ7OHUvQE8SXA6eg,467
|
|
@@ -73,12 +73,12 @@ tests/unit/test_metrics.py,sha256=PjjTrB9w7uQ2Q5UN-893-SsH3EGJuBseOMHSD1I004s,79
|
|
|
73
73
|
tests/unit/test_oauth.py,sha256=nbFG0JH1x04ExXqSe-b5BGdt_hJs7DP17eUa6bQzcYI,2093
|
|
74
74
|
tests/unit/test_plugin.py,sha256=ZTNAZWwZhDIAbxkVuWhnz9FzmojbijgMmsLWM2mXQI0,4160
|
|
75
75
|
tests/unit/test_schema.py,sha256=swWZPRo_Q4M6VHte-srmxcV2wH-XS7pgmNRxpaL0Qrg,642
|
|
76
|
-
tests/unit/test_server_cloud_plugins.py,sha256=
|
|
76
|
+
tests/unit/test_server_cloud_plugins.py,sha256=PuMDHTz3af6lR9QK1BtPScr7_cRbWhetowADieVlXdo,5096
|
|
77
77
|
tests/unit/test_server_spec.py,sha256=je97BaktgK0Fiz3AwFPkcmHzYtOJJNqJV_Fw5hrvqX4,644
|
|
78
78
|
tests/unit/test_trigger.py,sha256=E53mAUoVyponWu_4IQZ0IC1gQ9lakBnTn_9vKN2IZfg,1692
|
|
79
79
|
tests/unit/test_variables.py,sha256=OUEOqGYZA3Nd5oKk5GVY3hcrWKHpZpxysBJcO_v5gzs,291
|
|
80
|
-
tests/unit/utils.py,sha256
|
|
81
|
-
insightconnect_plugin_runtime-5.4.
|
|
82
|
-
insightconnect_plugin_runtime-5.4.
|
|
83
|
-
insightconnect_plugin_runtime-5.4.
|
|
84
|
-
insightconnect_plugin_runtime-5.4.
|
|
80
|
+
tests/unit/utils.py,sha256=g_phxLH96rNwsdrry8Y7SjT9oZCps7ussI-ofc9K_xw,478
|
|
81
|
+
insightconnect_plugin_runtime-5.4.4.dist-info/METADATA,sha256=BpiS7DqRutG4tJTDJcr7y9LvoxJl597-4yFswMpo2b4,12702
|
|
82
|
+
insightconnect_plugin_runtime-5.4.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
83
|
+
insightconnect_plugin_runtime-5.4.4.dist-info/top_level.txt,sha256=AJtyJOpiFzHxsbHUICTcUKXyrGQ3tZxhrEHsPjJBvEA,36
|
|
84
|
+
insightconnect_plugin_runtime-5.4.4.dist-info/RECORD,,
|
tests/plugin/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from requests.exceptions import HTTPError, Timeout, TooManyRedirects
|
|
1
|
+
from requests.exceptions import HTTPError, Timeout, TooManyRedirects, ConnectionError
|
|
2
2
|
from parameterized import parameterized
|
|
3
3
|
from unittest import TestCase, skip
|
|
4
4
|
from unittest.mock import patch, MagicMock
|
|
@@ -46,11 +46,22 @@ class TestServerCloudPlugins(TestCase):
|
|
|
46
46
|
|
|
47
47
|
self.plugin.tasks = None # reset tasks value
|
|
48
48
|
|
|
49
|
-
@parameterized.expand(
|
|
49
|
+
@parameterized.expand(
|
|
50
|
+
[
|
|
51
|
+
["error", HTTPError],
|
|
52
|
+
["error", Timeout],
|
|
53
|
+
["unexpected", TooManyRedirects],
|
|
54
|
+
["Connection refused", ConnectionError],
|
|
55
|
+
]
|
|
56
|
+
)
|
|
50
57
|
@patch("insightconnect_plugin_runtime.server.request_get")
|
|
51
58
|
@patch("structlog.get_logger")
|
|
52
|
-
@patch(
|
|
53
|
-
|
|
59
|
+
@patch(
|
|
60
|
+
"insightconnect_plugin_runtime.server.CPS_RETRY", new=2
|
|
61
|
+
) # reduce retries in unit tests
|
|
62
|
+
@patch(
|
|
63
|
+
"insightconnect_plugin_runtime.server.RETRY_SLEEP", new=1
|
|
64
|
+
) # reduce sleep in unit tests
|
|
54
65
|
def test_cps_raises_an_error(self, test_cond, exception, log, mocked_req, _mock_cloud, _run):
|
|
55
66
|
log.return_value = Logger()
|
|
56
67
|
# If we have successfully got config and scheduler options, and later this call fails we should keep values
|
|
@@ -63,11 +74,18 @@ class TestServerCloudPlugins(TestCase):
|
|
|
63
74
|
|
|
64
75
|
self.assertDictEqual(plugin_server.config_options, PLUGIN_VALUE_2)
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
if test_cond == "Connection refused":
|
|
78
|
+
mocked_req.side_effect = ConnectionError("Connection Refused")
|
|
79
|
+
plugin_server.get_plugin_properties_from_cps()
|
|
80
|
+
# we log error as info log, as this is likely to be hit when the pod is just starting up
|
|
81
|
+
self.assertIn(test_cond, plugin_server.logger.last_info[-1])
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
# First call has happened and now successful - force to hit specific handled and unexpected errors.
|
|
85
|
+
mocked_req.side_effect = exception("Warning HTTP error returned...")
|
|
86
|
+
plugin_server.get_plugin_properties_from_cps()
|
|
87
|
+
# we log error in all and `unexpected` in TooManyRedirects as there is no direct catch for this
|
|
88
|
+
self.assertIn(test_cond, plugin_server.logger.last_error[-2])
|
|
71
89
|
|
|
72
90
|
# Values should not have changed
|
|
73
91
|
self.assertDictEqual(plugin_server.config_options, PLUGIN_VALUE_2)
|
tests/unit/utils.py
CHANGED
|
@@ -10,11 +10,11 @@ class MockResponse:
|
|
|
10
10
|
class Logger:
|
|
11
11
|
"""Mocked logger to easily find last log triggered from SDK server."""
|
|
12
12
|
def __init__(self):
|
|
13
|
-
self.last_error =
|
|
14
|
-
self.last_info =
|
|
13
|
+
self.last_error = []
|
|
14
|
+
self.last_info = []
|
|
15
15
|
|
|
16
16
|
def info(self, log: str):
|
|
17
|
-
self.last_info
|
|
17
|
+
self.last_info.append(log)
|
|
18
18
|
|
|
19
19
|
def error(self, log: str):
|
|
20
|
-
self.last_error
|
|
20
|
+
self.last_error.append(log)
|
|
File without changes
|
|
File without changes
|