insightconnect-plugin-runtime 5.4.9__tar.gz → 5.5.0__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 (92) hide show
  1. {insightconnect_plugin_runtime-5.4.9/insightconnect_plugin_runtime.egg-info → insightconnect_plugin_runtime-5.5.0}/PKG-INFO +3 -2
  2. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/README.md +1 -0
  3. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/api/endpoints.py +33 -9
  4. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/clients/aws_client.py +41 -36
  5. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/exceptions.py +64 -8
  6. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/helper.py +259 -1
  7. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/plugin.py +25 -5
  8. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/server.py +37 -11
  9. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/util.py +3 -1
  10. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0/insightconnect_plugin_runtime.egg-info}/PKG-INFO +3 -2
  11. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime.egg-info/requires.txt +1 -1
  12. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/setup.py +2 -2
  13. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_helpers.py +215 -0
  14. insightconnect_plugin_runtime-5.5.0/tests/unit/utils.py +78 -0
  15. insightconnect_plugin_runtime-5.4.9/tests/unit/utils.py +0 -20
  16. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/MANIFEST.in +0 -0
  17. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect-plugin-swagger.json +0 -0
  18. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/__init__.py +0 -0
  19. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/action.py +0 -0
  20. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/api/__init__.py +0 -0
  21. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/api/schemas.py +0 -0
  22. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/cli.py +0 -0
  23. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/clients/__init__.py +0 -0
  24. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/clients/oauth.py +0 -0
  25. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/connection.py +0 -0
  26. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/data/input_message_schema.json +0 -0
  27. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/data/output_message_schema.json +0 -0
  28. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/dispatcher.py +0 -0
  29. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/metrics.py +0 -0
  30. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/schema.py +0 -0
  31. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/step.py +0 -0
  32. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/task.py +0 -0
  33. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/trigger.py +0 -0
  34. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime/variables.py +0 -0
  35. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime.egg-info/SOURCES.txt +0 -0
  36. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime.egg-info/dependency_links.txt +0 -0
  37. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/insightconnect_plugin_runtime.egg-info/top_level.txt +0 -0
  38. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/setup.cfg +0 -0
  39. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/__init__.py +0 -0
  40. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/__init__.py +0 -0
  41. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/__init__.py +0 -0
  42. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/__init__.py +0 -0
  43. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/__init__.py +0 -0
  44. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/__init__.py +0 -0
  45. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/hello/__init__.py +0 -0
  46. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/hello/action.py +0 -0
  47. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/hello/schema.py +0 -0
  48. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/return_bad_json/__init__.py +0 -0
  49. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/return_bad_json/action.py +0 -0
  50. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/return_bad_json/schema.py +0 -0
  51. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/throw_exception/__init__.py +0 -0
  52. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/throw_exception/action.py +0 -0
  53. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/actions/throw_exception/schema.py +0 -0
  54. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/connection/__init__.py +0 -0
  55. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/connection/connection.py +0 -0
  56. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/connection/schema.py +0 -0
  57. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/__init__.py +0 -0
  58. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/monitor_events/__init__.py +0 -0
  59. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/monitor_events/schema.py +0 -0
  60. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/tasks/monitor_events/task.py +0 -0
  61. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/__init__.py +0 -0
  62. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/hello_trigger/__init__.py +0 -0
  63. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/hello_trigger/schema.py +0 -0
  64. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/hello_trigger/trigger.py +0 -0
  65. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/return_bad_json_trigger/__init__.py +0 -0
  66. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/return_bad_json_trigger/schema.py +0 -0
  67. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/return_bad_json_trigger/trigger.py +0 -0
  68. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/throw_exception_trigger/__init__.py +0 -0
  69. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/throw_exception_trigger/schema.py +0 -0
  70. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/triggers/throw_exception_trigger/trigger.py +0 -0
  71. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/komand_hello_world/util/__init__.py +0 -0
  72. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/hello_world/setup.py +0 -0
  73. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/tests/__init__.py +0 -0
  74. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/tests/conftest.py +0 -0
  75. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/tests/test_cli.py +0 -0
  76. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/tests/test_hello_world.py +0 -0
  77. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/plugin/hello_world/tests/test_server.py +0 -0
  78. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/__init__.py +0 -0
  79. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_action.py +0 -0
  80. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_api.py +0 -0
  81. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_aws_action.py +0 -0
  82. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_custom_encoder.py +0 -0
  83. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_endpoints.py +0 -0
  84. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_exceptions.py +0 -0
  85. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_metrics.py +0 -0
  86. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_oauth.py +0 -0
  87. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_plugin.py +0 -0
  88. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_schema.py +0 -0
  89. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_server_cloud_plugins.py +0 -0
  90. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_server_spec.py +0 -0
  91. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_trigger.py +0 -0
  92. {insightconnect_plugin_runtime-5.4.9 → insightconnect_plugin_runtime-5.5.0}/tests/unit/test_variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: insightconnect-plugin-runtime
3
- Version: 5.4.9
3
+ Version: 5.5.0
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,7 +12,7 @@ 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.31.0
15
+ Requires-Dist: requests==2.32.0
16
16
  Requires-Dist: python_jsonschema_objects==0.5.2
17
17
  Requires-Dist: jsonschema==4.21.1
18
18
  Requires-Dist: certifi==2024.2.2
@@ -211,6 +211,7 @@ 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
+ * 5.5.0 - Updated helper class to add `make_request`, `response_handler`, `extract_json`, and `request_error_handling` for HTTP requests, and `hash_sha1` and `compare_and_dedupe_hashes` to provide support for hash comparisons | Add `METHOD_NOT_ALLOWED`, `CONFLICT`, `REDIRECT_ERROR`, and `CONNECTION_ERROR` to PluginException presets
214
215
  * 5.4.9 - Updated aws_client to clean assume role json object to remove any none or empty string values.
215
216
  * 5.4.8 - Address vulnerabilities within `gunicorn` and `idna` python packages.
216
217
  * 5.4.7 - Address vulnerabilities within `insightconnect-python-3-plugin` image.
@@ -182,6 +182,7 @@ 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
+ * 5.5.0 - Updated helper class to add `make_request`, `response_handler`, `extract_json`, and `request_error_handling` for HTTP requests, and `hash_sha1` and `compare_and_dedupe_hashes` to provide support for hash comparisons | Add `METHOD_NOT_ALLOWED`, `CONFLICT`, `REDIRECT_ERROR`, and `CONNECTION_ERROR` to PluginException presets
185
186
  * 5.4.9 - Updated aws_client to clean assume role json object to remove any none or empty string values.
186
187
  * 5.4.8 - Address vulnerabilities within `gunicorn` and `idna` python packages.
187
188
  * 5.4.7 - Address vulnerabilities within `insightconnect-python-3-plugin` image.
@@ -83,7 +83,17 @@ def handle_errors(error: HTTPException):
83
83
 
84
84
 
85
85
  class Endpoints:
86
- def __init__(self, logger, plugin, spec, debug, workers, threads, master_pid, config_options=None):
86
+ def __init__(
87
+ self,
88
+ logger,
89
+ plugin,
90
+ spec,
91
+ debug,
92
+ workers,
93
+ threads,
94
+ master_pid,
95
+ config_options=None,
96
+ ):
87
97
  self.plugin = plugin
88
98
  self.logger = structlog.get_logger("plugin")
89
99
  self.spec = spec
@@ -167,7 +177,9 @@ class Endpoints:
167
177
  Endpoints.validate_action_trigger_task_name(input_message, name, "task")
168
178
  # No validation on the plugin custom config to leave this as configurable as possible.
169
179
  # `add_plugin_custom_config` will pass any available values to the plugin for interpretation.
170
- input_message = self.add_plugin_custom_config(input_message, request.headers.get(ORG_ID))
180
+ input_message = self.add_plugin_custom_config(
181
+ input_message, request.headers.get(ORG_ID)
182
+ )
171
183
  output = self.run_action_trigger_task(input_message)
172
184
  self.logger.info("Plugin task finished execution...")
173
185
  return output
@@ -750,7 +762,9 @@ class Endpoints:
750
762
 
751
763
  return version
752
764
 
753
- def add_plugin_custom_config(self, input_data: Dict[str, Any], org_id: str) -> Dict[str, Any]:
765
+ def add_plugin_custom_config(
766
+ self, input_data: Dict[str, Any], org_id: str
767
+ ) -> Dict[str, Any]:
754
768
  """
755
769
  Using the retrieved configs pulled from komand-props, pass the configuration that matches the requesting
756
770
  Org ID which is passed to the task via a header (`X-IPIMS-ORGID`) from the plugin sidecar.
@@ -763,17 +777,27 @@ class Endpoints:
763
777
  - all orgs for default runs will poll back 12 hours.
764
778
  - all orgs in lookback mode will be 100 hours (task triggered with no state).
765
779
  """
766
- additional_config = self.config_options.get(org_id) or self.config_options.get("*")
780
+ additional_config = self.config_options.get(org_id) or self.config_options.get(
781
+ "*"
782
+ )
767
783
  if additional_config:
768
- self.logger.info("Found config options; adding this to the request parameters...")
769
- additional_config = additional_config.copy() # copy to preserve the referenced value in self.config_options
784
+ self.logger.info(
785
+ "Found config options; adding this to the request parameters..."
786
+ )
787
+ additional_config = (
788
+ additional_config.copy()
789
+ ) # copy to preserve the referenced value in self.config_options
770
790
  # As a safeguard we only pass the lookback config params if the plugin has no state
771
791
  # This means we still need to manually delete the state for plugins on a per org basis.
772
792
  # This also means first time customers for their 'initial' lookup would get the lookback value passed in.
773
- if input_data.get("body", {}).get("state") and additional_config.get("lookback"):
774
- self.logger.info("Found an existing plugin state, not passing lookback value...")
793
+ if input_data.get("body", {}).get("state") and additional_config.get(
794
+ "lookback"
795
+ ):
796
+ self.logger.info(
797
+ "Found an existing plugin state, not passing lookback value..."
798
+ )
775
799
  del additional_config["lookback"]
776
- input_data.get("body", {}).update({'custom_config': additional_config})
800
+ input_data.get("body", {}).update({"custom_config": additional_config})
777
801
  self.logger.info(f"Custom config being sent to plugin: {additional_config}")
778
802
 
779
803
  return input_data
@@ -29,13 +29,13 @@ class PaginationHelper:
29
29
  """
30
30
 
31
31
  def __init__(
32
- self,
33
- input_token: List[str],
34
- output_token: List[str],
35
- result_key: List[str],
36
- limit_key: str = None,
37
- more_results: str = None,
38
- non_aggregate_keys: List[str] = None,
32
+ self,
33
+ input_token: List[str],
34
+ output_token: List[str],
35
+ result_key: List[str],
36
+ limit_key: str = None,
37
+ more_results: str = None,
38
+ non_aggregate_keys: List[str] = None,
39
39
  ):
40
40
  self.input_token = input_token
41
41
  self.output_token = output_token
@@ -70,9 +70,9 @@ class PaginationHelper:
70
70
  is_paginated = False
71
71
 
72
72
  if (
73
- self.more_results
74
- and self.more_results in output.keys()
75
- and output[self.more_results]
73
+ self.more_results
74
+ and self.more_results in output.keys()
75
+ and output[self.more_results]
76
76
  ):
77
77
  is_paginated = True
78
78
 
@@ -84,10 +84,10 @@ class PaginationHelper:
84
84
  return is_paginated
85
85
 
86
86
  def merge_responses(
87
- self,
88
- input_: Dict[str, Any],
89
- response_1: Dict[str, Any],
90
- response_2: Dict[str, Any],
87
+ self,
88
+ input_: Dict[str, Any],
89
+ response_1: Dict[str, Any],
90
+ response_2: Dict[str, Any],
91
91
  ) -> Tuple[Dict[str, Any], bool]:
92
92
  """
93
93
  Merges two output dictionaries together.
@@ -112,7 +112,8 @@ class PaginationHelper:
112
112
  if len(response_1[response]) >= input_[self.limit_key]:
113
113
  max_hit = True
114
114
  response_1[response] = response_1[response][
115
- : input_[self.limit_key]]
115
+ : input_[self.limit_key]
116
+ ]
116
117
 
117
118
  return response_1, max_hit
118
119
 
@@ -302,7 +303,7 @@ class ActionHelper:
302
303
 
303
304
  @classmethod
304
305
  def format_output(
305
- cls, output_schema: Union[Dict[str, Any], None], output: Dict[str, Any]
306
+ cls, output_schema: Union[Dict[str, Any], None], output: Dict[str, Any]
306
307
  ) -> Any:
307
308
  """
308
309
  Formats a botocore response into a correct Komand response.
@@ -333,14 +334,14 @@ class AWSAction(Action):
333
334
  """
334
335
 
335
336
  def __init__(
336
- self,
337
- name: str,
338
- description: str,
339
- input_: insightconnect_plugin_runtime.Input,
340
- output: insightconnect_plugin_runtime.Output,
341
- aws_service: str,
342
- aws_command: str,
343
- pagination_helper: PaginationHelper = None,
337
+ self,
338
+ name: str,
339
+ description: str,
340
+ input_: insightconnect_plugin_runtime.Input,
341
+ output: insightconnect_plugin_runtime.Output,
342
+ aws_service: str,
343
+ aws_command: str,
344
+ pagination_helper: PaginationHelper = None,
344
345
  ):
345
346
  """
346
347
 
@@ -362,7 +363,7 @@ class AWSAction(Action):
362
363
  self.pagination_helper = pagination_helper
363
364
 
364
365
  def _handle_botocore_function(
365
- self, client_function: Callable, params: Dict
366
+ self, client_function: Callable, params: Dict
366
367
  ) -> Dict:
367
368
  try:
368
369
  response = client_function(**params)
@@ -480,11 +481,11 @@ class AWSAction(Action):
480
481
  client_function = getattr(client, self.aws_command)
481
482
  except AttributeError:
482
483
  error_message = (
483
- 'Unable to find the command "'
484
- + self.aws_service
485
- + " "
486
- + self.aws_command
487
- + '"'
484
+ 'Unable to find the command "'
485
+ + self.aws_service
486
+ + " "
487
+ + self.aws_command
488
+ + '"'
488
489
  )
489
490
  self.logger.error(error_message)
490
491
  raise PluginException(cause=error_message)
@@ -533,17 +534,21 @@ class AWSAction(Action):
533
534
 
534
535
  @staticmethod
535
536
  def try_to_assume_role(
536
- service_name: str,
537
- assume_role_params: Dict[str, str],
538
- auth_params: Dict[str, str],
537
+ service_name: str,
538
+ assume_role_params: Dict[str, str],
539
+ auth_params: Dict[str, str],
539
540
  ):
540
541
  session_name = str(uuid.uuid1())
541
542
  sts_client = boto3.client("sts", **auth_params)
542
543
  try:
543
544
  assumed_role_object = sts_client.assume_role(
544
- **clean({"RoleArn": assume_role_params.get(ROLE_ARN),
545
- "RoleSessionName": session_name,
546
- "ExternalId": assume_role_params.get(EXTERNAL_ID)})
545
+ **clean(
546
+ {
547
+ "RoleArn": assume_role_params.get(ROLE_ARN),
548
+ "RoleSessionName": session_name,
549
+ "ExternalId": assume_role_params.get(EXTERNAL_ID),
550
+ }
551
+ )
547
552
  )
548
553
  except ClientError as error:
549
554
  raise PluginException(
@@ -1,6 +1,56 @@
1
1
  # -*- coding: utf-8 -*-
2
- import structlog
3
- logger = structlog.get_logger("plugin")
2
+ class ResponseExceptionData:
3
+ RESPONSE_TEXT = "response_text"
4
+ RESPONSE_JSON = "response_json"
5
+ RESPONSE = "response"
6
+ EXCEPTION = "exception"
7
+
8
+
9
+ class HTTPStatusCodes:
10
+
11
+ # 4xx Client Errors
12
+ BAD_REQUEST = 400
13
+ UNAUTHORIZED = 401
14
+ PAYMENT_REQUIRED = 402
15
+ FORBIDDEN = 403
16
+ NOT_FOUND = 404
17
+ METHOD_NOT_ALLOWED = 405
18
+ NOT_ACCEPTABLE = 406
19
+ PROXY_AUTHENTICATION_REQUIRED = 407
20
+ REQUEST_TIMEOUT = 408
21
+ CONFLICT = 409
22
+ GONE = 410
23
+ LENGTH_REQUIRED = 411
24
+ PRECONDITION_FAILED = 412
25
+ PAYLOAD_TOO_LARGE = 413
26
+ URI_TOO_LONG = 414
27
+ UNSUPPORTED_MEDIA_TYPE = 415
28
+ RANGE_NOT_SATISFIABLE = 416
29
+ EXPECTATION_FAILED = 417
30
+ I_AM_A_TEAPOT = 418
31
+ MISDIRECTED_REQUEST = 421
32
+ UNPROCESSABLE_ENTITY = 422
33
+ LOCKED = 423
34
+ FAILED_DEPENDENCY = 424
35
+ TOO_EARLY = 425
36
+ UPGRADE_REQUIRED = 426
37
+ PRECONDITION_REQUIRED = 428
38
+ TOO_MANY_REQUESTS = 429
39
+ REQUEST_HEADER_FIELDS_TOO_LARGE = 431
40
+ UNAVAILABLE_FOR_LEGAL_REASONS = 451
41
+
42
+ # 5xx Server Errors
43
+ INTERNAL_SERVER_ERROR = 500
44
+ NOT_IMPLEMENTED = 501
45
+ BAD_GATEWAY = 502
46
+ SERVICE_UNAVAILABLE = 503
47
+ GATEWAY_TIMEOUT = 504
48
+ HTTP_VERSION_NOT_SUPPORTED = 505
49
+ VARIANT_ALSO_NEGOTIATES = 506
50
+ INSUFFICIENT_STORAGE = 507
51
+ LOOP_DETECTED = 508
52
+ NOT_EXTENDED = 510
53
+ NETWORK_AUTHENTICATION_REQUIRED = 511
4
54
 
5
55
 
6
56
  class ClientException(Exception):
@@ -65,6 +115,10 @@ class ConnectionTestException(Exception):
65
115
  TIMEOUT = "timeout"
66
116
  BAD_REQUEST = "bad_request"
67
117
  INVALID_CREDENTIALS = "invalid_credentials"
118
+ METHOD_NOT_ALLOWED = "method_not_allowed"
119
+ CONFLICT = "conflict"
120
+ CONNECTION_ERROR = "connection_error"
121
+ REDIRECT_ERROR = "redirect_error"
68
122
 
69
123
  # Dictionary of cause messages
70
124
  causes = {
@@ -82,6 +136,10 @@ class ConnectionTestException(Exception):
82
136
  Preset.TIMEOUT: "The connection timed out.",
83
137
  Preset.BAD_REQUEST: "The server is unable to process the request.",
84
138
  Preset.INVALID_CREDENTIALS: "Authentication failed: invalid credentials.",
139
+ Preset.METHOD_NOT_ALLOWED: "The request method is not allowed for this resource.",
140
+ Preset.CONFLICT: "Request cannot be completed due to a conflict with the current state of the resource.",
141
+ Preset.CONNECTION_ERROR: "Failed to connect to the server.",
142
+ Preset.REDIRECT_ERROR: "Request redirected more than the set limit for the server.",
85
143
  }
86
144
 
87
145
  # Dictionary of assistance/remediation messages
@@ -103,6 +161,10 @@ class ConnectionTestException(Exception):
103
161
  Preset.BAD_REQUEST: "Verify your plugin input is correct and not malformed and try again. "
104
162
  "If the issue persists, please contact support.",
105
163
  Preset.INVALID_CREDENTIALS: "Please verify the credentials for your account and try again.",
164
+ Preset.METHOD_NOT_ALLOWED: "Please try a supported method for this resource.",
165
+ Preset.CONFLICT: "Please check your request, and try again.",
166
+ Preset.CONNECTION_ERROR: "Please check your network connection and try again.",
167
+ Preset.REDIRECT_ERROR: "Please check your request and try again.",
106
168
  }
107
169
 
108
170
  def __init__(self, cause=None, assistance=None, data=None, preset=None):
@@ -124,12 +186,6 @@ class ConnectionTestException(Exception):
124
186
 
125
187
  self.data = str(data) if data else ""
126
188
 
127
- # Safeguard to ensure the exception is logged across all plugins even if the plugin
128
- # itself does not call `self.logger.error(<error info>)`
129
- params = ["cause", "assistance", "data", "preset"]
130
- info_log = ", ".join([f"{atr}='{getattr(self, atr)}'" for atr in params if getattr(self, atr)])
131
- logger.error(f"Plugin exception instantiated. {info_log}")
132
-
133
189
  def __str__(self):
134
190
  if self.data:
135
191
  return "Connection test failed!\n\n{cause} {assistance} Response was: {data}".format(
@@ -12,18 +12,276 @@ from datetime import datetime, timedelta
12
12
  from io import IOBase
13
13
  from typing import Any, Callable, Dict, List, Union
14
14
  from urllib import request
15
+ from hashlib import sha1
16
+ from json import JSONDecodeError
15
17
 
16
18
  import requests
17
19
 
18
- from insightconnect_plugin_runtime.exceptions import PluginException
20
+ from insightconnect_plugin_runtime.exceptions import (
21
+ PluginException,
22
+ HTTPStatusCodes,
23
+ ResponseExceptionData,
24
+ )
19
25
 
20
26
  CAMEL_CASE_REGEX = r"\b[a-z0-9]+([A-Z][a-z]+[0-9]*)*\b"
21
27
  CAMEL_CASE_ACRONYM_REGEX = r"\b[a-z0-9]+([A-Z]+[0-9]*)*\b"
22
28
  PASCAL_CASE_REGEX = r"\b[A-Z][a-z]+[0-9]*([A-Z][a-z]+[0-9]*)*\b"
29
+ ENCODE_TYPE = "utf-8"
23
30
 
24
31
  DEFAULTS_HOURS_AGO = 24
25
32
 
26
33
 
34
+ def hash_sha1(log: dict) -> str:
35
+ """
36
+ Iterate through a dictionary and hash each value.
37
+ :param log: Dictionary to be hashed.
38
+ :type dict:
39
+ :return: Hex digest of hash.
40
+ :rtype: str
41
+ """
42
+ hash_ = sha1() # nosec B303
43
+ for key, value in log.items():
44
+ hash_.update(f"{key}{value}".encode(ENCODE_TYPE))
45
+ return hash_.hexdigest()
46
+
47
+
48
+ def compare_and_dedupe_hashes(
49
+ previous_logs_hashes: list, new_logs: list
50
+ ) -> tuple[list, list]:
51
+ """
52
+ Iterate through two lists of values, hashing each. Compare hash value to a list of existing hash values.
53
+ If the hash exists, return both it and the value in separate lists once iterated.
54
+ :param previous_logs_hashes: List of existing hashes to compare against.
55
+ :type list:
56
+ :param new_logs: New values to hash and compare to existing list of hashes.
57
+ :type list:
58
+ :return: Hex digest of hash.
59
+ :rtype: tuple[list, list]
60
+ """
61
+ new_logs_hashes = []
62
+ logs_to_return = []
63
+ for log in new_logs:
64
+ hash_ = hash_sha1(log)
65
+ if hash_ not in previous_logs_hashes:
66
+ new_logs_hashes.append(hash_)
67
+ logs_to_return.append(log)
68
+ logging.info(
69
+ f"Original number of logs:{len(new_logs)}. Number of logs after de-duplication:{len(logs_to_return)}"
70
+ )
71
+ return logs_to_return, new_logs_hashes
72
+
73
+
74
+ def make_request(
75
+ _request: requests.Request,
76
+ timeout: int = 60,
77
+ verify: bool = True,
78
+ cert: Union[str, tuple]=None,
79
+ stream: bool = False,
80
+ allow_redirects: bool = True,
81
+ exception_custom_configs: Dict[int, Exception]={},
82
+ exception_data_location: str = None,
83
+ allowed_status_codes: list[str] = [],
84
+ ) -> tuple[requests.Response, dict]:
85
+ """
86
+ Makes a HTTP request while checking for RequestErrors and JSONDecodeErrors
87
+ Returns the request response and the response JSON if required.
88
+ :param _request: Request object to utilize in request
89
+ :type Request:
90
+ :param timeout: Requests timeout paramater
91
+ :type int:
92
+ :param verify: Whether to verify the server's TLS certificate
93
+ :type bool:
94
+ :param cert: Certificate to include with request, str location or key/value pair
95
+ :type Union[str, dict]:
96
+ :param stream: Whether to immediately download the response content
97
+ :type bool:
98
+ :param allow_redirects: Set to true by default
99
+ :type bool:
100
+ :param exception_custom_configs: Custom exception values to be raised per HTTPStatusCode.
101
+ :type Dict[str, Exception]:
102
+ :param exception_data_location: Where the returned data should be retrieved. Can provide ResponseExceptionData values.
103
+ :type str:
104
+ :param allowed_status_codes: Status codes that will not raise an exception.
105
+ :type list[str]:
106
+
107
+ :return: The request response and the response JSON.
108
+ :rtype: tuple[Response, dict]
109
+ """
110
+ try:
111
+ with requests.Session() as session:
112
+ prepared_request = session.prepare_request(request=_request)
113
+ response = session.send(
114
+ prepared_request,
115
+ verify=verify,
116
+ timeout=timeout,
117
+ allow_redirects=allow_redirects,
118
+ cert=cert,
119
+ stream=stream,
120
+ )
121
+ except requests.exceptions.Timeout as exception:
122
+ raise PluginException(
123
+ preset=PluginException.Preset.TIMEOUT, data=str(exception)
124
+ )
125
+ except requests.exceptions.ConnectionError as exception:
126
+ raise PluginException(
127
+ preset=PluginException.Preset.CONNECTION_ERROR, data=str(exception)
128
+ )
129
+ except requests.exceptions.TooManyRedirects as exception:
130
+ raise PluginException(
131
+ preset=PluginException.Preset.REDIRECT_ERROR, data=str(exception)
132
+ )
133
+ except requests.exceptions.RequestException as exception:
134
+ if not isinstance(exception, requests.exceptions.HTTPError):
135
+ raise PluginException(
136
+ preset=PluginException.Preset.UNKNOWN, data=str(exception)
137
+ )
138
+ response_handler(response, exception_custom_configs, exception_data_location, allowed_status_codes)
139
+ return response
140
+
141
+
142
+ def extract_json(response: requests.Response) -> dict:
143
+ """Extract JSON from a request object while error handling a JSONDecodeError.
144
+ :param response: Response object ot utilize in extract
145
+ :type Response:
146
+ :returns: Dictionary of response JSON
147
+ :rtype: Dict
148
+ """
149
+ try:
150
+ response_json = response.json()
151
+ return response_json
152
+ except JSONDecodeError as exception:
153
+ raise PluginException(
154
+ preset=PluginException.Preset.INVALID_JSON, data=str(exception)
155
+ )
156
+
157
+
158
+ def request_error_handling() -> Union[Any, None]:
159
+ """request_error_handling. This decorator allows a method that makes a request to complete with error handling.
160
+ A plugin exception will be raised whenever an error is caught. Response.raise_for_status() must be called in the
161
+ wrapped method to handle HTTPErrors.
162
+
163
+ :returns: API call function data or None.
164
+ :rtype: Union[Any, None]
165
+ """
166
+
167
+ def _decorate(func: Callable):
168
+ def _wrapper(self, *args, **kwargs):
169
+ try:
170
+ return func(self, *args, **kwargs)
171
+ except requests.exceptions.Timeout as exception:
172
+ raise PluginException(
173
+ preset=PluginException.Preset.TIMEOUT, data=str(exception)
174
+ )
175
+ except requests.exceptions.ConnectionError as exception:
176
+ raise PluginException(
177
+ preset=PluginException.Preset.CONNECTION_ERROR, data=str(exception)
178
+ )
179
+ except requests.exceptions.TooManyRedirects as exception:
180
+ raise PluginException(
181
+ preset=PluginException.Preset.REDIRECT_ERROR, data=str(exception)
182
+ )
183
+ except requests.exceptions.RequestException as exception:
184
+ if isinstance(exception, requests.exceptions.HTTPError):
185
+ response_handler(exception.response)
186
+ else:
187
+ raise PluginException(
188
+ preset=PluginException.Preset.UNKNOWN, data=str(exception)
189
+ )
190
+
191
+ return _wrapper
192
+
193
+ return _decorate
194
+
195
+
196
+ def response_handler(
197
+ response: requests.Response,
198
+ custom_configs: Dict[int, Exception]={},
199
+ data_location: str = None,
200
+ allowed_status_codes: list[str] = [],
201
+ ) -> None:
202
+ """
203
+ Check response status codes and return a generic PluginException preset if a HTTPError is raised.
204
+ Excetion cause, assistance, and data can be overwritten by supplied parameters.
205
+
206
+ :param response: Response object whose status should be checked.
207
+ :type Response:
208
+ :param custom_configs: Custom exception values to be raised per HTTPStatusCode.
209
+ :type Dict[str, Exception]:
210
+ :param data_location: Where the returned data should be retrieved. Can provide ResponseExceptionData values.
211
+ :type str:
212
+ :param allowed_status_codes: Status codes that will not raise an exception.
213
+ :type list[str]:
214
+
215
+ :return: None.
216
+ :rtype: None
217
+ """
218
+ try:
219
+ response.raise_for_status()
220
+ except requests.exceptions.HTTPError as exception:
221
+
222
+ data = _return_response_data(response, exception, data_location)
223
+ status_code = response.status_code
224
+ if status_code in allowed_status_codes:
225
+ return
226
+ status_code_presets = {
227
+ HTTPStatusCodes.BAD_REQUEST: PluginException.Preset.BAD_REQUEST,
228
+ HTTPStatusCodes.UNAUTHORIZED: PluginException.Preset.INVALID_CREDENTIALS,
229
+ HTTPStatusCodes.FORBIDDEN: PluginException.Preset.UNAUTHORIZED,
230
+ HTTPStatusCodes.NOT_FOUND: PluginException.Preset.NOT_FOUND,
231
+ HTTPStatusCodes.METHOD_NOT_ALLOWED: PluginException.Preset.METHOD_NOT_ALLOWED,
232
+ HTTPStatusCodes.REQUEST_TIMEOUT: PluginException.Preset.TIMEOUT,
233
+ HTTPStatusCodes.CONFLICT: PluginException.Preset.CONFLICT,
234
+ HTTPStatusCodes.TOO_MANY_REQUESTS: PluginException.Preset.RATE_LIMIT,
235
+ HTTPStatusCodes.INTERNAL_SERVER_ERROR: PluginException.Preset.SERVER_ERROR,
236
+ HTTPStatusCodes.SERVICE_UNAVAILABLE: PluginException.Preset.SERVICE_UNAVAILABLE,
237
+ }
238
+ status_code_preset = status_code_presets.get(status_code)
239
+ exception = PluginException(preset=PluginException.Preset.UNKNOWN, data=data)
240
+ logging.info(f"Request to {response.url} failed. Status code: {status_code}")
241
+ if status_code in custom_configs.keys():
242
+ exception = custom_configs.get(status_code)
243
+ if hasattr(exception, "data") and data is not None:
244
+ exception.data = data
245
+ elif status_code_preset:
246
+ exception = PluginException(preset=status_code_preset, data=data)
247
+
248
+ raise exception
249
+
250
+
251
+ def _return_response_data(
252
+ response: requests.Response,
253
+ exception: requests.HTTPError,
254
+ data_location: str = None,
255
+ ) -> str:
256
+ """
257
+ Retrieve data from HTTP Error given a provided data location
258
+
259
+ :param response: Response object whose status should be checked.
260
+ :type Response:
261
+ :param exception: HTTPError exception to retrieve data.
262
+ :type HTTPError:
263
+ :param data_location: Where the returned data should be retrieved. Can provide ResponseExceptionData values.
264
+ :type str:
265
+
266
+ :return: Exception data.
267
+ :rtype: str
268
+ """
269
+ data = None
270
+ if data_location == ResponseExceptionData.EXCEPTION:
271
+ data = str(exception)
272
+ elif data_location == ResponseExceptionData.RESPONSE:
273
+ data = response
274
+ elif data_location == ResponseExceptionData.RESPONSE_TEXT:
275
+ data = response.text
276
+ elif data_location == ResponseExceptionData.RESPONSE_JSON:
277
+ try:
278
+ data = response.json()
279
+ except JSONDecodeError:
280
+ # Return full exception if JSON cannot be resolved
281
+ data = exception
282
+ return data
283
+
284
+
27
285
  def extract_value(begin, key, end, s):
28
286
  """
29
287
  Returns a string from a given key/pattern using provided regular expressions.